diff --git a/.github/workflows/ci-matrix.yml b/.github/workflows/ci-matrix.yml index 3b7c44c7a..3d7e47af5 100644 --- a/.github/workflows/ci-matrix.yml +++ b/.github/workflows/ci-matrix.yml @@ -4,7 +4,7 @@ on: pull_request: types: [opened, reopened, synchronize] branches: - - master + - GH-784 jobs: build: diff --git a/.idea/modules.xml b/.idea/modules.xml index c32b128d9..861973a36 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -10,6 +10,7 @@ + \ No newline at end of file diff --git a/automap/Cargo.lock b/automap/Cargo.lock index c970dc200..b2aaa5d2a 100644 --- a/automap/Cargo.lock +++ b/automap/Cargo.lock @@ -464,6 +464,27 @@ dependencies = [ "subtle 1.0.0", ] +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + [[package]] name = "cxx" version = "1.0.94" @@ -901,6 +922,15 @@ dependencies = [ "libc", ] +[[package]] +name = "ip_country" +version = "0.1.0" +dependencies = [ + "csv", + "itertools 0.13.0", + "lazy_static", +] + [[package]] name = "ipconfig" version = "0.1.9" @@ -923,6 +953,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.6" @@ -1059,7 +1098,8 @@ dependencies = [ "crossbeam-channel 0.5.8", "dirs", "ethereum-types", - "itertools", + "ip_country", + "itertools 0.10.5", "lazy_static", "log 0.4.17", "nix", @@ -1067,6 +1107,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "test_utilities", "time 0.3.20", "tiny-hderive", "toml", @@ -1927,6 +1968,10 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test_utilities" +version = "0.1.0" + [[package]] name = "textwrap" version = "0.11.0" diff --git a/ci/all.sh b/ci/all.sh index 2f381c45c..6591cd392 100755 --- a/ci/all.sh +++ b/ci/all.sh @@ -8,7 +8,7 @@ ci/format.sh export RUST_BACKTRACE=1 # Remove these two lines to slow down the build -which sccache || cargo install sccache || echo "Skipping sccache installation" # Should do significant work only once +which sccache || cargo install --version 0.4.1 sccache || echo "Skipping sccache installation" # Should do significant work only once #export CARGO_TARGET_DIR="$CI_DIR/../cargo-cache" export SCCACHE_DIR="$HOME/.cargo/sccache" #export RUSTC_WRAPPER="$HOME/.cargo/bin/sccache" diff --git a/dns_utility/Cargo.lock b/dns_utility/Cargo.lock index 52a3ec6b3..f1701c7d9 100644 --- a/dns_utility/Cargo.lock +++ b/dns_utility/Cargo.lock @@ -399,6 +399,27 @@ dependencies = [ "subtle 1.0.0", ] +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + [[package]] name = "digest" version = "0.8.1" @@ -716,6 +737,15 @@ dependencies = [ "libc", ] +[[package]] +name = "ip_country" +version = "0.1.0" +dependencies = [ + "csv", + "itertools 0.13.0", + "lazy_static", +] + [[package]] name = "ipconfig" version = "0.1.9" @@ -750,6 +780,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.6" @@ -862,7 +901,8 @@ dependencies = [ "crossbeam-channel 0.5.8", "dirs", "ethereum-types", - "itertools", + "ip_country", + "itertools 0.10.5", "lazy_static", "log 0.4.17", "nix", @@ -870,6 +910,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "test_utilities", "time 0.3.20", "tiny-hderive", "toml", @@ -1653,6 +1694,10 @@ dependencies = [ "libc", ] +[[package]] +name = "test_utilities" +version = "0.1.0" + [[package]] name = "textwrap" version = "0.11.0" diff --git a/ip_country/Cargo.toml b/ip_country/Cargo.toml index 7ba43fb60..0e57d7cac 100644 --- a/ip_country/Cargo.toml +++ b/ip_country/Cargo.toml @@ -13,8 +13,9 @@ workspace = "../node" csv = "1.3.0" itertools = "0.13.0" lazy_static = "1.4.0" -masq_lib = { path = "../masq_lib" } +[dev-dependencies] +test_utilities = { path = "../test_utilities"} [[bin]] name = "ip_country" diff --git a/ip_country/src/country_finder.rs b/ip_country/src/country_finder.rs index 270ecd76c..114c89421 100644 --- a/ip_country/src/country_finder.rs +++ b/ip_country/src/country_finder.rs @@ -54,9 +54,7 @@ impl CountryCodeFinder { #[cfg(test)] mod tests { use super::*; - use crate::country_block_serde::{ - CountryBlockDeserializer, Ipv4CountryBlockDeserializer, Ipv6CountryBlockDeserializer, - }; + use crate::country_block_serde::{Ipv4CountryBlockDeserializer, Ipv6CountryBlockDeserializer}; use crate::dbip_country; use std::str::FromStr; use std::time::SystemTime; diff --git a/ip_country/src/ip_country.rs b/ip_country/src/ip_country.rs index abf461aba..f4a7cbe13 100644 --- a/ip_country/src/ip_country.rs +++ b/ip_country/src/ip_country.rs @@ -133,8 +133,8 @@ fn write_value( #[cfg(test)] mod tests { use super::*; - use masq_lib::test_utils::fake_stream_holder::{ByteArrayReader, ByteArrayWriter}; use std::io::{Error, ErrorKind}; + use test_utilities::byte_array_reader_writer::{ByteArrayReader, ByteArrayWriter}; static PROPER_TEST_DATA: &str = "0.0.0.0,0.255.255.255,ZZ 1.0.0.0,1.0.0.255,AU diff --git a/masq/Cargo.toml b/masq/Cargo.toml index 87a20ad67..dd9863021 100644 --- a/masq/Cargo.toml +++ b/masq/Cargo.toml @@ -28,6 +28,7 @@ nix = "0.23.0" [dev-dependencies] atty = "0.2.14" +test_utilities = { path = "../test_utilities" } [lib] name = "masq_cli_lib" diff --git a/masq/src/command_context.rs b/masq/src/command_context.rs index 41308e5bb..e648da218 100644 --- a/masq/src/command_context.rs +++ b/masq/src/command_context.rs @@ -156,12 +156,12 @@ mod tests { 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}; use masq_lib::test_utils::mock_websockets_server::MockWebSocketsServer; use masq_lib::ui_gateway::MessageBody; use masq_lib::ui_gateway::MessagePath::Conversation; use masq_lib::ui_traffic_converter::{TrafficConversionError, UnmarshalError}; use masq_lib::utils::{find_free_port, running_test}; + use test_utilities::byte_array_reader_writer::{ByteArrayReader, ByteArrayWriter}; #[test] fn constant_has_correct_values() { diff --git a/masq/src/command_factory.rs b/masq/src/command_factory.rs index 21222c454..6ec658903 100644 --- a/masq/src/command_factory.rs +++ b/masq/src/command_factory.rs @@ -8,6 +8,7 @@ use crate::commands::configuration_command::ConfigurationCommand; use crate::commands::connection_status_command::ConnectionStatusCommand; use crate::commands::crash_command::CrashCommand; use crate::commands::descriptor_command::DescriptorCommand; +use crate::commands::exit_location_command::SetExitLocationCommand; use crate::commands::financials_command::FinancialsCommand; use crate::commands::generate_wallets_command::GenerateWalletsCommand; use crate::commands::recover_wallets_command::RecoverWalletsCommand; @@ -52,6 +53,10 @@ impl CommandFactory for CommandFactoryReal { Err(msg) => return Err(CommandSyntax(msg)), }, "descriptor" => Box::new(DescriptorCommand::new()), + "exit-location" => match SetExitLocationCommand::new(pieces) { + Ok(command) => Box::new(command), + Err(msg) => return Err(CommandSyntax(msg)), + }, "financials" => match FinancialsCommand::new(pieces) { Ok(command) => Box::new(command), Err(msg) => return Err(CommandSyntax(msg)), @@ -103,6 +108,7 @@ impl CommandFactoryReal { mod tests { use super::*; use crate::command_factory::CommandFactoryError::UnrecognizedSubcommand; + use masq_lib::messages::CountryCodes; #[test] fn complains_about_unrecognized_subcommand() { @@ -258,6 +264,34 @@ mod tests { ); } + #[test] + fn factory_produces_exit_location() { + let subject = CommandFactoryReal::new(); + + let command = subject + .make(&[ + "exit-location".to_string(), + "--country-codes".to_string(), + "CZ".to_string(), + ]) + .unwrap(); + + assert_eq!( + command + .as_any() + .downcast_ref::() + .unwrap(), + &SetExitLocationCommand { + exit_locations: vec![CountryCodes { + country_codes: vec!["CZ".to_string()], + priority: 1 + }], + fallback_routing: false, + show_countries: false, + } + ); + } + #[test] fn complains_about_set_configuration_command_with_no_parameters() { let subject = CommandFactoryReal::new(); diff --git a/masq/src/commands/exit_location_command.rs b/masq/src/commands/exit_location_command.rs new file mode 100644 index 000000000..552bab768 --- /dev/null +++ b/masq/src/commands/exit_location_command.rs @@ -0,0 +1,274 @@ +use crate::command_context::CommandContext; +use crate::commands::commands_common::{ + transaction, Command, CommandError, STANDARD_COMMAND_TIMEOUT_MILLIS, +}; +use clap::{App, Arg, SubCommand}; +use masq_lib::as_any_ref_in_trait_impl; +use masq_lib::messages::{CountryCodes, UiSetExitLocationRequest, UiSetExitLocationResponse}; +use masq_lib::shared_schema::common_validators; + +const EXIT_LOCATION_ABOUT: &str = + "If you activate exit-location preferences, all exit Nodes in countries you don't specify will be prohibited: \n\ + that is, if there is no exit Node available in any of your preferred countries, you'll get an error. However, \ + if you just want to make a suggestion, and you don't mind Nodes in other countries being used if nothing is available \ + in your preferred countries, you can specify --fallback-routing, and you'll get no error unless there are no exit Nodes \ + available anywhere.\n\n\ + Here are some example commands:\n\ + masq> exit-location // disable exit-location preferences\n\ + masq> exit-location --fallback-routing // disable exit-location preferences\n\ + masq> exit-location --country-codes \"CZ,PL|SK\" --fallback-routing \n\t// fallback-routing is ON, \"CZ\" and \"PL\" countries have same priority \"1\", \"SK\" has priority \"2\"\n\ + masq> exit-location --country-codes \"CZ|SK\" \n\t// fallback-routing is OFF, \"CZ\" and \"SK\" countries have different priority\n"; + +// TODO update following help when GH-469 is done with `To obtain codes, you can use the 'country-codes-list' (469 card command) command.` +const COUNTRY_CODES_HELP: &str = "Establish a set of countries that your Node should try to use for exit Nodes. You should choose from the countries that host the \ + Nodes in your Neighborhood. List the countries in order of preference, separated by vertical pipes (|). If your level of preference \ + for a group of countries is the same, separate those countries by commas (,).\n\ + You can specify country codes as follows:\n\n\ + masq> exit-location --country-codes \"CZ,PL|SK\" \n\t// \"CZ\" and \"PL\" countries have same priority \"1\", \"SK\" has priority \"2\" \n\ + masq> exit-location --country-codes \"CZ|SK\" \n\t// \"CZ\" and \"SK\" countries have different priority\n\n"; + +const FALLBACK_ROUTING_HELP: &str = "If you just want to make a suggestion, and you don't mind Nodes in other countries being used if nothing is available \ + in your preferred countries, you can specify --fallback-routing, and you'll get no error unless there are no exit Nodes \ + available anywhere. \n Here are some examples: \n\n\ + masq> exit-location --country-codes \"CZ\" --fallback-routing \n\t// Set exit-location for \"CZ\" country with fallback-routing on \n\ + masq> exit-location --country-codes \"CZ\" \n\t// Set exit-location for \"CZ\" country with fallback-routing off \n\n"; + +pub fn exit_location_subcommand() -> App<'static, 'static> { + SubCommand::with_name("exit-location").about(EXIT_LOCATION_ABOUT) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct SetExitLocationCommand { + pub exit_locations: Vec, + pub fallback_routing: bool, + pub show_countries: bool, +} + +impl SetExitLocationCommand { + pub fn new(pieces: &[String]) -> Result { + match set_exit_location_subcommand().get_matches_from_safe(pieces) { + Ok(matches) => { + let exit_locations = match matches.is_present("country-codes") { + true => matches + .values_of("country-codes") + .expect("Expected Country Codes") + .into_iter() + .enumerate() + .map(|(index, code)| CountryCodes::from((code.to_string(), index))) + .collect(), + false => vec![], + }; + let fallback_routing = !matches!( + ( + matches.is_present("fallback-routing"), + matches.is_present("country-codes") + ), + (false, true) + ); + let show_countries = matches.is_present("show-countries"); + Ok(SetExitLocationCommand { + exit_locations, + fallback_routing, + show_countries, + }) + } + + Err(e) => Err(format!("SetExitLocationCommand {}", e)), + } + } +} + +impl Command for SetExitLocationCommand { + fn execute(&self, context: &mut dyn CommandContext) -> Result<(), CommandError> { + let input = UiSetExitLocationRequest { + exit_locations: self.exit_locations.clone(), + fallback_routing: self.fallback_routing, + show_countries: self.show_countries, + }; + + let _: UiSetExitLocationResponse = + transaction(input, context, STANDARD_COMMAND_TIMEOUT_MILLIS)?; + Ok(()) + } + + as_any_ref_in_trait_impl!(); +} + +pub fn set_exit_location_subcommand() -> App<'static, 'static> { + SubCommand::with_name("exit-location") + .about(EXIT_LOCATION_ABOUT) + .arg( + Arg::with_name("country-codes") + .long("country-codes") + .value_name("COUNTRY-CODES") + .value_delimiter("|") + .validator(common_validators::validate_exit_locations) + .help(COUNTRY_CODES_HELP) + .required(false), + ) + .arg( + Arg::with_name("fallback-routing") + .long("fallback-routing") + .value_name("FALLBACK-ROUTING") + .help(FALLBACK_ROUTING_HELP) + .takes_value(false) + .required(false), + ) + .arg( + Arg::with_name("show-countries") + .long("show-countries") + .value_name("SHOW-COUNTRIES") + .takes_value(false) + .required(false), + ) +} + +#[cfg(test)] +pub mod tests { + use crate::commands::commands_common::{Command, STANDARD_COMMAND_TIMEOUT_MILLIS}; + use crate::commands::exit_location_command::SetExitLocationCommand; + use crate::test_utils::mocks::CommandContextMock; + use masq_lib::messages::{ + CountryCodes, ToMessageBody, UiSetExitLocationRequest, UiSetExitLocationResponse, + }; + use std::sync::{Arc, Mutex}; + + #[test] + fn can_deserialize_ui_set_exit_location() { + let transact_params_arc = Arc::new(Mutex::new(vec![])); + let mut context = CommandContextMock::new() + .transact_params(&transact_params_arc) + .transact_result(Ok(UiSetExitLocationResponse {}.tmb(0))); + let stderr_arc = context.stderr_arc(); + let subject = SetExitLocationCommand::new(&[ + "exit-location".to_string(), + "--country-codes".to_string(), + "CZ,SK|AT,DE|PL".to_string(), + "--fallback-routing".to_string(), + ]) + .unwrap(); + + let result = subject.execute(&mut context); + + assert_eq!(result, Ok(())); + let expected_request = UiSetExitLocationRequest { + fallback_routing: true, + exit_locations: vec![ + CountryCodes { + country_codes: vec!["CZ".to_string(), "SK".to_string()], + priority: 1, + }, + CountryCodes { + country_codes: vec!["AT".to_string(), "DE".to_string()], + priority: 2, + }, + CountryCodes { + country_codes: vec!["PL".to_string()], + priority: 3, + }, + ], + show_countries: false, + }; + let transact_params = transact_params_arc.lock().unwrap(); + let expected_message_body = expected_request.tmb(0); + assert_eq!( + transact_params.as_slice(), + &[(expected_message_body, STANDARD_COMMAND_TIMEOUT_MILLIS)] + ); + let stderr = stderr_arc.lock().unwrap(); + assert_eq!(&stderr.get_string(), ""); + } + + #[test] + fn absence_of_fallback_routing_produces_fallback_routing_false() { + let transact_params_arc = Arc::new(Mutex::new(vec![])); + let mut context = CommandContextMock::new() + .transact_params(&transact_params_arc) + .transact_result(Ok(UiSetExitLocationResponse {}.tmb(0))); + let stderr_arc = context.stderr_arc(); + let subject = SetExitLocationCommand::new(&[ + "exit-location".to_string(), + "--country-codes".to_string(), + "CZ".to_string(), + ]) + .unwrap(); + + let result = subject.execute(&mut context); + + assert_eq!(result, Ok(())); + let expected_request = UiSetExitLocationRequest { + fallback_routing: false, + exit_locations: vec![CountryCodes { + country_codes: vec!["CZ".to_string()], + priority: 1, + }], + show_countries: false, + }; + let transact_params = transact_params_arc.lock().unwrap(); + let expected_message_body = expected_request.tmb(0); + assert_eq!( + transact_params.as_slice(), + &[(expected_message_body, STANDARD_COMMAND_TIMEOUT_MILLIS)] + ); + let stderr = stderr_arc.lock().unwrap(); + assert_eq!(&stderr.get_string(), ""); + } + + #[test] + fn providing_no_arguments_cause_exit_location_reset_request() { + let transact_params_arc = Arc::new(Mutex::new(vec![])); + let mut context = CommandContextMock::new() + .transact_params(&transact_params_arc) + .transact_result(Ok(UiSetExitLocationResponse {}.tmb(0))); + let stderr_arc = context.stderr_arc(); + let subject = SetExitLocationCommand::new(&["exit-location".to_string()]).unwrap(); + + let result = subject.execute(&mut context); + + assert_eq!(result, Ok(())); + let expected_request = UiSetExitLocationRequest { + fallback_routing: true, + exit_locations: vec![], + show_countries: false, + }; + let transact_params = transact_params_arc.lock().unwrap(); + let expected_message_body = expected_request.tmb(0); + assert_eq!( + transact_params.as_slice(), + &[(expected_message_body, STANDARD_COMMAND_TIMEOUT_MILLIS)] + ); + let stderr = stderr_arc.lock().unwrap(); + assert_eq!(&stderr.get_string(), ""); + } + + #[test] + fn providing_show_countries_cause_request_for_list_of_countries() { + let transact_params_arc = Arc::new(Mutex::new(vec![])); + let mut context = CommandContextMock::new() + .transact_params(&transact_params_arc) + .transact_result(Ok(UiSetExitLocationResponse {}.tmb(0))); + let stderr_arc = context.stderr_arc(); + let subject = SetExitLocationCommand::new(&[ + "exit-location".to_string(), + "--show-countries".to_string(), + ]) + .unwrap(); + + let result = subject.execute(&mut context); + + assert_eq!(result, Ok(())); + let expected_request = UiSetExitLocationRequest { + fallback_routing: true, + exit_locations: vec![], + show_countries: true, + }; + let transact_params = transact_params_arc.lock().unwrap(); + let expected_message_body = expected_request.tmb(0); + assert_eq!( + transact_params.as_slice(), + &[(expected_message_body, STANDARD_COMMAND_TIMEOUT_MILLIS)] + ); + let stderr = stderr_arc.lock().unwrap(); + assert_eq!(&stderr.get_string(), ""); + } +} diff --git a/masq/src/commands/mod.rs b/masq/src/commands/mod.rs index 44026ebf8..143dff8e1 100644 --- a/masq/src/commands/mod.rs +++ b/masq/src/commands/mod.rs @@ -7,6 +7,7 @@ pub mod configuration_command; pub mod connection_status_command; pub mod crash_command; pub mod descriptor_command; +pub mod exit_location_command; pub mod financials_command; pub mod generate_wallets_command; pub mod recover_wallets_command; diff --git a/masq/src/commands/set_configuration_command.rs b/masq/src/commands/set_configuration_command.rs index 99d979f07..dbe17e753 100644 --- a/masq/src/commands/set_configuration_command.rs +++ b/masq/src/commands/set_configuration_command.rs @@ -84,6 +84,8 @@ pub fn set_configuration_subcommand() -> App<'static, 'static> { .args(&["gas-price", "min-hops", "start-block"]) .required(true), ) + //TODO here is the place we want to place function to set country_code for ExitService, + // this function will be used in shared_app to setup the country_code for ExitService on startup } #[cfg(test)] diff --git a/masq/src/interactive_mode.rs b/masq/src/interactive_mode.rs index a9141ffa5..026ed4aef 100644 --- a/masq/src/interactive_mode.rs +++ b/masq/src/interactive_mode.rs @@ -168,10 +168,11 @@ mod tests { CommandFactoryMock, CommandProcessorMock, TerminalActiveMock, TerminalPassiveMock, }; use crossbeam_channel::bounded; - use masq_lib::test_utils::fake_stream_holder::{ByteArrayWriter, FakeStreamHolder}; + use masq_lib::test_utils::fake_stream_holder::FakeStreamHolder; use std::sync::{Arc, Mutex}; use std::thread; use std::time::{Duration, Instant}; + use test_utilities::byte_array_reader_writer::ByteArrayWriter; #[test] fn interactive_mode_works_for_unrecognized_command() { diff --git a/masq/src/notifications/connection_change_notification.rs b/masq/src/notifications/connection_change_notification.rs index 9ea7d5911..0480660fb 100644 --- a/masq/src/notifications/connection_change_notification.rs +++ b/masq/src/notifications/connection_change_notification.rs @@ -34,9 +34,9 @@ impl ConnectionChangeNotification { mod tests { use super::*; use crate::test_utils::mocks::TerminalPassiveMock; - use masq_lib::test_utils::fake_stream_holder::ByteArrayWriter; use masq_lib::utils::running_test; use std::sync::Arc; + use test_utilities::byte_array_reader_writer::ByteArrayWriter; #[test] fn broadcasts_connected_to_neighbor() { diff --git a/masq/src/notifications/crashed_notification.rs b/masq/src/notifications/crashed_notification.rs index 36e0ff4f4..66dfc773c 100644 --- a/masq/src/notifications/crashed_notification.rs +++ b/masq/src/notifications/crashed_notification.rs @@ -63,9 +63,9 @@ impl CrashNotifier { mod tests { use super::*; use crate::test_utils::mocks::TerminalPassiveMock; - use masq_lib::test_utils::fake_stream_holder::ByteArrayWriter; use masq_lib::utils::running_test; use std::sync::Arc; + use test_utilities::byte_array_reader_writer::ByteArrayWriter; #[test] pub fn handles_child_wait_failure() { diff --git a/masq/src/schema.rs b/masq/src/schema.rs index c03a3ea39..287fd9468 100644 --- a/masq/src/schema.rs +++ b/masq/src/schema.rs @@ -8,6 +8,7 @@ use crate::commands::configuration_command::configuration_subcommand; use crate::commands::connection_status_command::connection_status_subcommand; use crate::commands::crash_command::crash_subcommand; use crate::commands::descriptor_command::descriptor_subcommand; +use crate::commands::exit_location_command::exit_location_subcommand; use crate::commands::financials_command::args_validation::financials_subcommand; use crate::commands::generate_wallets_command::generate_wallets_subcommand; use crate::commands::recover_wallets_command::recover_wallets_subcommand; @@ -67,6 +68,7 @@ pub fn app() -> App<'static, 'static> { .subcommand(configuration_subcommand()) .subcommand(connection_status_subcommand()) .subcommand(descriptor_subcommand()) + .subcommand(exit_location_subcommand()) .subcommand(financials_subcommand()) .subcommand(generate_wallets_subcommand()) .subcommand(recover_wallets_subcommand()) diff --git a/masq/src/terminal/integration_test_utils.rs b/masq/src/terminal/integration_test_utils.rs index 7f42f72f5..526e19a66 100644 --- a/masq/src/terminal/integration_test_utils.rs +++ b/masq/src/terminal/integration_test_utils.rs @@ -187,9 +187,9 @@ mod tests { use crate::terminal::terminal_interface::TerminalWrapper; use crate::test_utils::mocks::StdoutBlender; use crossbeam_channel::{bounded, unbounded}; - use masq_lib::test_utils::fake_stream_holder::ByteArrayReader; use std::thread; use std::time::Duration; + use test_utilities::byte_array_reader_writer::ByteArrayReader; #[test] fn constants_have_correct_values() { diff --git a/masq/src/test_utils/mocks.rs b/masq/src/test_utils/mocks.rs index 955fa578a..9f70b7e5b 100644 --- a/masq/src/test_utils/mocks.rs +++ b/masq/src/test_utils/mocks.rs @@ -15,7 +15,6 @@ use linefeed::memory::MemoryTerminal; use linefeed::{Interface, ReadResult, Signal}; use masq_lib::command::StdStreams; use masq_lib::constants::DEFAULT_UI_PORT; -use masq_lib::test_utils::fake_stream_holder::{ByteArrayWriter, ByteArrayWriterInner}; use masq_lib::ui_gateway::MessageBody; use std::cell::RefCell; use std::fmt::Arguments; @@ -23,6 +22,7 @@ use std::io::{Read, Write}; use std::sync::{Arc, Mutex}; use std::time::Duration; use std::{io, thread}; +use test_utilities::byte_array_reader_writer::{ByteArrayWriter, ByteArrayWriterInner}; pub const TRANSACT_TIMEOUT_MILLIS_FOR_TESTS: u64 = DEFAULT_TRANSACT_TIMEOUT_MILLIS; diff --git a/masq_lib/Cargo.toml b/masq_lib/Cargo.toml index f87de81a1..0f36bf8a9 100644 --- a/masq_lib/Cargo.toml +++ b/masq_lib/Cargo.toml @@ -15,12 +15,14 @@ crossbeam-channel = "0.5.1" dirs = "4.0.0" ethereum-types = "0.9.0" itertools = "0.10.1" +ip_country = { path = "../ip_country"} lazy_static = "1.4.0" log = "0.4.8" regex = "1.5.4" serde = "1.0.133" serde_derive = "1.0.133" serde_json = "1.0.74" +test_utilities = { path = "../test_utilities"} time = {version = "0.3.11", features = [ "formatting" ]} tiny-hderive = "0.3.0" toml = "0.5.8" diff --git a/masq_lib/src/blockchains/blockchain_records.rs b/masq_lib/src/blockchains/blockchain_records.rs index f9b4bcab0..5227195f7 100644 --- a/masq_lib/src/blockchains/blockchain_records.rs +++ b/masq_lib/src/blockchains/blockchain_records.rs @@ -2,15 +2,14 @@ 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, - MUMBAI_TESTNET_CONTRACT_CREATION_BLOCK, POLYGON_MAINNET_CONTRACT_CREATION_BLOCK, - POLYGON_MAINNET_FULL_IDENTIFIER, POLYGON_MUMBAI_FULL_IDENTIFIER, + AMOY_TESTNET_CONTRACT_CREATION_BLOCK, DEV_CHAIN_FULL_IDENTIFIER, + ETH_MAINNET_CONTRACT_CREATION_BLOCK, ETH_MAINNET_FULL_IDENTIFIER, ETH_ROPSTEN_FULL_IDENTIFIER, + MULTINODE_TESTNET_CONTRACT_CREATION_BLOCK, POLYGON_AMOY_FULL_IDENTIFIER, + POLYGON_MAINNET_CONTRACT_CREATION_BLOCK, POLYGON_MAINNET_FULL_IDENTIFIER, ROPSTEN_TESTNET_CONTRACT_CREATION_BLOCK, }; use ethereum_types::{Address, H160}; -//chains are ordered by their significance for the community of users (the order reflects in some error or help messages) pub const CHAINS: [BlockchainRecord; 5] = [ BlockchainRecord { self_id: Chain::PolyMainnet, @@ -27,11 +26,11 @@ pub const CHAINS: [BlockchainRecord; 5] = [ contract_creation_block: ETH_MAINNET_CONTRACT_CREATION_BLOCK, }, BlockchainRecord { - self_id: Chain::PolyMumbai, - num_chain_id: 80001, - literal_identifier: POLYGON_MUMBAI_FULL_IDENTIFIER, - contract: MUMBAI_TESTNET_CONTRACT_ADDRESS, - contract_creation_block: MUMBAI_TESTNET_CONTRACT_CREATION_BLOCK, + self_id: Chain::PolyAmoy, + num_chain_id: 80002, + literal_identifier: POLYGON_AMOY_FULL_IDENTIFIER, + contract: AMOY_TESTNET_CONTRACT_ADDRESS, + contract_creation_block: AMOY_TESTNET_CONTRACT_CREATION_BLOCK, }, BlockchainRecord { self_id: Chain::EthRopsten, @@ -58,6 +57,22 @@ pub struct BlockchainRecord { pub contract_creation_block: u64, } +const POLYGON_MAINNET_CONTRACT_ADDRESS: Address = H160([ + 0xee, 0x9a, 0x35, 0x2f, 0x6a, 0xac, 0x4a, 0xf1, 0xa5, 0xb9, 0xf4, 0x67, 0xf6, 0xa9, 0x3e, 0x0f, + 0xfb, 0xe9, 0xdd, 0x35, +]); + +const ETH_MAINNET_CONTRACT_ADDRESS: Address = H160([ + 0x06, 0xf3, 0xc3, 0x23, 0xf0, 0x23, 0x8c, 0x72, 0xbf, 0x35, 0x01, 0x10, 0x71, 0xf2, 0xb5, 0xb7, + 0xf4, 0x3a, 0x05, 0x4c, +]); + +// $tMASQ (Amoy) +const AMOY_TESTNET_CONTRACT_ADDRESS: Address = H160([ + 0xd9, 0x8c, 0x3e, 0xbd, 0x6b, 0x7f, 0x9b, 0x7c, 0xda, 0x24, 0x49, 0xec, 0xac, 0x00, 0xd1, 0xe5, + 0xf4, 0x7a, 0x81, 0x93, +]); + // SHRD (Ropsten) const ROPSTEN_TESTNET_CONTRACT_ADDRESS: Address = H160([ 0x38, 0x4d, 0xec, 0x25, 0xe0, 0x3f, 0x94, 0x93, 0x17, 0x67, 0xce, 0x4c, 0x35, 0x56, 0x16, 0x84, @@ -69,30 +84,12 @@ const MULTINODE_TESTNET_CONTRACT_ADDRESS: Address = H160([ 0xf1, 0xb3, 0xe6, 0x64, ]); -const ETH_MAINNET_CONTRACT_ADDRESS: Address = H160([ - 0x06, 0xF3, 0xC3, 0x23, 0xf0, 0x23, 0x8c, 0x72, 0xBF, 0x35, 0x01, 0x10, 0x71, 0xf2, 0xb5, 0xB7, - 0xF4, 0x3A, 0x05, 0x4c, -]); - -#[allow(clippy::mixed_case_hex_literals)] -const POLYGON_MAINNET_CONTRACT_ADDRESS: Address = H160([ - 0xEe, 0x9A, 0x35, 0x2F, 0x6a, 0xAc, 0x4a, 0xF1, 0xA5, 0xB9, 0xf4, 0x67, 0xF6, 0xa9, 0x3E, 0x0f, - 0xfB, 0xe9, 0xDd, 0x35, -]); - -// $tMASQ (Mumbai) -#[allow(clippy::mixed_case_hex_literals)] -const MUMBAI_TESTNET_CONTRACT_ADDRESS: Address = H160([ - 0x9B, 0x27, 0x03, 0x4a, 0xca, 0xBd, 0x44, 0x22, 0x3f, 0xB2, 0x3d, 0x62, 0x8B, 0xa4, 0x84, 0x98, - 0x67, 0xcE, 0x1D, 0xB2, -]); - #[cfg(test)] mod tests { use super::*; use crate::blockchains::chains::chain_from_chain_identifier_opt; use crate::constants::{ - MUMBAI_TESTNET_CONTRACT_CREATION_BLOCK, POLYGON_MAINNET_CONTRACT_CREATION_BLOCK, + AMOY_TESTNET_CONTRACT_CREATION_BLOCK, POLYGON_MAINNET_CONTRACT_CREATION_BLOCK, }; use std::collections::HashSet; use std::iter::FromIterator; @@ -104,7 +101,7 @@ mod tests { assert_returns_correct_record(Chain::Dev, 2), assert_returns_correct_record(Chain::EthRopsten, 3), assert_returns_correct_record(Chain::PolyMainnet, 137), - assert_returns_correct_record(Chain::PolyMumbai, 80001), + assert_returns_correct_record(Chain::PolyAmoy, 80002), ]; assert_exhaustive(&test_array) } @@ -118,7 +115,7 @@ mod tests { fn from_str_works() { let test_array = [ assert_from_str(Chain::PolyMainnet), - assert_from_str(Chain::PolyMumbai), + assert_from_str(Chain::PolyAmoy), assert_from_str(Chain::EthMainnet), assert_from_str(Chain::EthRopsten), assert_from_str(Chain::Dev), @@ -142,7 +139,7 @@ mod tests { let test_array = [ assert_chain_significance(0, Chain::PolyMainnet), assert_chain_significance(1, Chain::EthMainnet), - assert_chain_significance(2, Chain::PolyMumbai), + assert_chain_significance(2, Chain::PolyAmoy), assert_chain_significance(3, Chain::EthRopsten), assert_chain_significance(4, Chain::Dev), ]; @@ -203,17 +200,17 @@ mod tests { } #[test] - fn mumbai_record_is_properly_declared() { - let examined_chain = Chain::PolyMumbai; + fn amoy_record_is_properly_declared() { + let examined_chain = Chain::PolyAmoy; let chain_record = return_examined(examined_chain); assert_eq!( chain_record, &BlockchainRecord { - num_chain_id: 80001, + num_chain_id: 80002, self_id: examined_chain, - literal_identifier: "polygon-mumbai", - contract: MUMBAI_TESTNET_CONTRACT_ADDRESS, - contract_creation_block: MUMBAI_TESTNET_CONTRACT_CREATION_BLOCK, + literal_identifier: "polygon-amoy", + contract: AMOY_TESTNET_CONTRACT_ADDRESS, + contract_creation_block: AMOY_TESTNET_CONTRACT_CREATION_BLOCK, } ); } @@ -245,7 +242,7 @@ mod tests { assert_chain_from_chain_identifier_opt("eth-ropsten", Some(Chain::EthRopsten)), assert_chain_from_chain_identifier_opt("dev", Some(Chain::Dev)), assert_chain_from_chain_identifier_opt("polygon-mainnet", Some(Chain::PolyMainnet)), - assert_chain_from_chain_identifier_opt("polygon-mumbai", Some(Chain::PolyMumbai)), + assert_chain_from_chain_identifier_opt("polygon-amoy", Some(Chain::PolyAmoy)), ]; assert_exhaustive(&test_array) } diff --git a/masq_lib/src/blockchains/chains.rs b/masq_lib/src/blockchains/chains.rs index ac3fbbac0..433d5524c 100644 --- a/masq_lib/src/blockchains/chains.rs +++ b/masq_lib/src/blockchains/chains.rs @@ -3,7 +3,7 @@ use crate::blockchains::blockchain_records::{BlockchainRecord, CHAINS}; use crate::constants::{ DEFAULT_CHAIN, DEV_CHAIN_FULL_IDENTIFIER, ETH_MAINNET_FULL_IDENTIFIER, - ETH_ROPSTEN_FULL_IDENTIFIER, POLYGON_MAINNET_FULL_IDENTIFIER, POLYGON_MUMBAI_FULL_IDENTIFIER, + ETH_ROPSTEN_FULL_IDENTIFIER, POLYGON_AMOY_FULL_IDENTIFIER, POLYGON_MAINNET_FULL_IDENTIFIER, }; use serde_derive::{Deserialize, Serialize}; @@ -12,7 +12,7 @@ pub enum Chain { EthMainnet, EthRopsten, PolyMainnet, - PolyMumbai, + PolyAmoy, Dev, } @@ -28,8 +28,8 @@ impl From<&str> for Chain { Chain::PolyMainnet } else if str == ETH_MAINNET_FULL_IDENTIFIER { Chain::EthMainnet - } else if str == POLYGON_MUMBAI_FULL_IDENTIFIER { - Chain::PolyMumbai + } else if str == POLYGON_AMOY_FULL_IDENTIFIER { + Chain::PolyAmoy } else if str == ETH_ROPSTEN_FULL_IDENTIFIER { Chain::EthRopsten } else if str == DEV_CHAIN_FULL_IDENTIFIER { diff --git a/masq_lib/src/constants.rs b/masq_lib/src/constants.rs index 7bdcbe1e1..6d758a4c2 100644 --- a/masq_lib/src/constants.rs +++ b/masq_lib/src/constants.rs @@ -26,9 +26,9 @@ pub const WEIS_IN_GWEI: i128 = 1_000_000_000; pub const ETH_MAINNET_CONTRACT_CREATION_BLOCK: u64 = 11_170_708; pub const ROPSTEN_TESTNET_CONTRACT_CREATION_BLOCK: u64 = 8_688_171; -pub const MULTINODE_TESTNET_CONTRACT_CREATION_BLOCK: u64 = 0; pub const POLYGON_MAINNET_CONTRACT_CREATION_BLOCK: u64 = 14_863_650; -pub const MUMBAI_TESTNET_CONTRACT_CREATION_BLOCK: u64 = 24_638_838; +pub const AMOY_TESTNET_CONTRACT_CREATION_BLOCK: u64 = 5_323_366; +pub const MULTINODE_TESTNET_CONTRACT_CREATION_BLOCK: u64 = 0; //Migration versions //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -71,6 +71,7 @@ pub const UNMARSHAL_ERROR: u64 = UI_NODE_COMMUNICATION_PREFIX | 4; pub const SETUP_ERROR: u64 = UI_NODE_COMMUNICATION_PREFIX | 5; pub const TIMEOUT_ERROR: u64 = UI_NODE_COMMUNICATION_PREFIX | 6; pub const SCAN_ERROR: u64 = UI_NODE_COMMUNICATION_PREFIX | 7; +pub const EXIT_COUNTRY_ERROR: u64 = UI_NODE_COMMUNICATION_PREFIX | 8; //accountant pub const ACCOUNTANT_PREFIX: u64 = 0x0040_0000_0000_0000; @@ -87,15 +88,15 @@ pub const CENTRAL_DELIMITER: char = '@'; pub const CHAIN_IDENTIFIER_DELIMITER: char = ':'; //chains -const MAINNET: &str = "mainnet"; const POLYGON_FAMILY: &str = "polygon"; const ETH_FAMILY: &str = "eth"; +const MAINNET: &str = "mainnet"; const LINK: char = '-'; pub const POLYGON_MAINNET_FULL_IDENTIFIER: &str = concatcp!(POLYGON_FAMILY, LINK, MAINNET); -pub const POLYGON_MUMBAI_FULL_IDENTIFIER: &str = concatcp!(POLYGON_FAMILY, LINK, "mumbai"); -pub const DEV_CHAIN_FULL_IDENTIFIER: &str = "dev"; +pub const POLYGON_AMOY_FULL_IDENTIFIER: &str = concatcp!(POLYGON_FAMILY, LINK, "amoy"); pub const ETH_MAINNET_FULL_IDENTIFIER: &str = concatcp!(ETH_FAMILY, LINK, MAINNET); pub const ETH_ROPSTEN_FULL_IDENTIFIER: &str = concatcp!(ETH_FAMILY, LINK, "ropsten"); +pub const DEV_CHAIN_FULL_IDENTIFIER: &str = "dev"; #[cfg(test)] mod tests { @@ -119,9 +120,9 @@ mod tests { assert_eq!(WEIS_IN_GWEI, 1_000_000_000); assert_eq!(ETH_MAINNET_CONTRACT_CREATION_BLOCK, 11_170_708); assert_eq!(ROPSTEN_TESTNET_CONTRACT_CREATION_BLOCK, 8_688_171); - assert_eq!(MULTINODE_TESTNET_CONTRACT_CREATION_BLOCK, 0); assert_eq!(POLYGON_MAINNET_CONTRACT_CREATION_BLOCK, 14_863_650); - assert_eq!(MUMBAI_TESTNET_CONTRACT_CREATION_BLOCK, 24_638_838); + assert_eq!(AMOY_TESTNET_CONTRACT_CREATION_BLOCK, 5_323_366); + assert_eq!(MULTINODE_TESTNET_CONTRACT_CREATION_BLOCK, 0); assert_eq!(CONFIGURATOR_PREFIX, 0x0001_0000_0000_0000); assert_eq!(CONFIGURATOR_READ_ERROR, CONFIGURATOR_PREFIX | 1); assert_eq!(CONFIGURATOR_WRITE_ERROR, CONFIGURATOR_PREFIX | 2); @@ -157,15 +158,15 @@ mod tests { assert_eq!(VALUE_EXCEEDS_ALLOWED_LIMIT, ACCOUNTANT_PREFIX | 3); assert_eq!(CENTRAL_DELIMITER, '@'); assert_eq!(CHAIN_IDENTIFIER_DELIMITER, ':'); - assert_eq!(MAINNET, "mainnet"); assert_eq!(POLYGON_FAMILY, "polygon"); assert_eq!(ETH_FAMILY, "eth"); + assert_eq!(MAINNET, "mainnet"); assert_eq!(LINK, '-'); assert_eq!(POLYGON_MAINNET_FULL_IDENTIFIER, "polygon-mainnet"); - assert_eq!(POLYGON_MUMBAI_FULL_IDENTIFIER, "polygon-mumbai"); - assert_eq!(DEV_CHAIN_FULL_IDENTIFIER, "dev"); + assert_eq!(POLYGON_AMOY_FULL_IDENTIFIER, "polygon-amoy"); assert_eq!(ETH_MAINNET_FULL_IDENTIFIER, "eth-mainnet"); assert_eq!(ETH_ROPSTEN_FULL_IDENTIFIER, "eth-ropsten"); + assert_eq!(DEV_CHAIN_FULL_IDENTIFIER, "dev"); assert_eq!( CLIENT_REQUEST_PAYLOAD_CURRENT_VERSION, DataVersion { major: 0, minor: 1 } diff --git a/masq_lib/src/messages.rs b/masq_lib/src/messages.rs index 59522171e..1366db4d6 100644 --- a/masq_lib/src/messages.rs +++ b/masq_lib/src/messages.rs @@ -846,6 +846,44 @@ pub struct UiWalletAddressesResponse { } conversation_message!(UiWalletAddressesResponse, "walletAddresses"); +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct CountryCodes { + #[serde(rename = "countryCodes")] + pub country_codes: Vec, + #[serde(rename = "priority")] + pub priority: usize, +} + +impl From<(String, usize)> for CountryCodes { + fn from((item, priority): (String, usize)) -> Self { + CountryCodes { + country_codes: item + .split(',') + .into_iter() + .map(|x| x.to_string()) + .collect::>(), + priority: priority + 1, + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct UiSetExitLocationRequest { + #[serde(rename = "fallbackRouting")] + pub fallback_routing: bool, + #[serde(rename = "exitLocations")] + pub exit_locations: Vec, + #[serde(rename = "showCountries")] + pub show_countries: bool, +} + +conversation_message!(UiSetExitLocationRequest, "exitLocation"); + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct UiSetExitLocationResponse {} + +conversation_message!(UiSetExitLocationResponse, "exitLocation"); + #[cfg(test)] mod tests { use super::*; diff --git a/masq_lib/src/shared_schema.rs b/masq_lib/src/shared_schema.rs index fc7e8ced2..3c619e429 100644 --- a/masq_lib/src/shared_schema.rs +++ b/masq_lib/src/shared_schema.rs @@ -3,7 +3,7 @@ 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, - POLYGON_MAINNET_FULL_IDENTIFIER, POLYGON_MUMBAI_FULL_IDENTIFIER, + POLYGON_AMOY_FULL_IDENTIFIER, POLYGON_MAINNET_FULL_IDENTIFIER, }; use crate::crash_point::CrashPoint; use clap::{App, Arg}; @@ -13,6 +13,7 @@ pub const BLOCKCHAIN_SERVICE_HELP: &str = "The Ethereum client you wish to use to provide Blockchain \ exit services from your MASQ Node (e.g. http://localhost:8545, \ https://ropsten.infura.io/v3/YOUR-PROJECT-ID, https://mainnet.infura.io/v3/YOUR-PROJECT-ID), \ + https://base-mainnet.g.alchemy.com/v2/d66UL0lPrltmweEqVsv3opBSVI3wkL8I, \ https://polygon-mainnet.infura.io/v3/YOUR-PROJECT-ID"; pub const CHAIN_HELP: &str = "The blockchain network MASQ Node will configure itself to use. You must ensure the \ @@ -64,8 +65,9 @@ pub const NEIGHBORS_HELP: &str = "One or more Node descriptors for running Nodes on startup. A Node descriptor looks similar to one of these:\n\n\ masq://polygon-mainnet:d2U3Dv1BqtS5t_Zz3mt9_sCl7AgxUlnkB4jOMElylrU@172.50.48.6:9342\n\ masq://eth-mainnet:gBviQbjOS3e5ReFQCvIhUM3i02d1zPleo1iXg_EN6zQ@86.75.30.9:5542\n\ - masq://polygon-mumbai:A6PGHT3rRjaeFpD_rFi3qGEXAVPq7bJDfEUZpZaIyq8@14.10.50.6:10504\n\ - masq://eth-ropsten:OHsC2CAm4rmfCkaFfiynwxflUgVTJRb2oY5mWxNCQkY@150.60.42.72:6642/4789/5254\n\n\ + masq://base-mainnet:ZjPLnb9RrgsRM1D9edqH8jx9DkbPZSWqqFqLnmdKhsk@112.55.78.0:7878\n\ + masq://polygon-amoy:A6PGHT3rRjaeFpD_rFi3qGEXAVPq7bJDfEUZpZaIyq8@14.10.50.6:10504\n\ + masq://base-sepolia:OHsC2CAm4rmfCkaFfiynwxflUgVTJRb2oY5mWxNCQkY@150.60.42.72:6642/4789/5254\n\n\ Notice each of the different chain identifiers in the masq protocol prefix - they determine a family of chains \ and also the network the descriptor belongs to (mainnet or a testnet). See also the last descriptor which shows \ a configuration with multiple clandestine ports.\n\n\ @@ -256,7 +258,7 @@ pub fn official_chain_names() -> &'static [&'static str] { &[ POLYGON_MAINNET_FULL_IDENTIFIER, ETH_MAINNET_FULL_IDENTIFIER, - POLYGON_MUMBAI_FULL_IDENTIFIER, + POLYGON_AMOY_FULL_IDENTIFIER, ETH_ROPSTEN_FULL_IDENTIFIER, DEV_CHAIN_FULL_IDENTIFIER, ] @@ -388,7 +390,6 @@ pub fn shared_app(head: App<'static, 'static>) -> App<'static, 'static> { .case_insensitive(true) .hidden(true), ) - .arg(data_directory_arg(DATA_DIRECTORY_HELP)) .arg(db_password_arg(DB_PASSWORD_HELP)) .arg( Arg::with_name("dns-servers") @@ -484,6 +485,7 @@ pub fn shared_app(head: App<'static, 'static>) -> App<'static, 'static> { pub mod common_validators { use crate::constants::LOWEST_USABLE_INSECURE_PORT; + use ip_country_lib::countries::INDEX_BY_ISO3166; use regex::Regex; use std::net::IpAddr; use std::str::FromStr; @@ -520,6 +522,44 @@ pub mod common_validators { } } + pub fn validate_country_code(country_code: &str) -> Result<(), String> { + match INDEX_BY_ISO3166.contains_key(country_code) { + true => Ok(()), + false => Err(format!( + "'{}' is not a valid ISO3166 country code", + country_code + )), + } + } + + pub fn validate_exit_locations(exit_location: String) -> Result<(), String> { + validate_pipe_separated_values(exit_location, |country: String| { + let mut collect_fails = "".to_string(); + country.split(',').into_iter().for_each(|country_code| { + match validate_country_code(country_code) { + Ok(_) => (), + Err(e) => collect_fails.push_str(&e), + } + }); + match collect_fails.is_empty() { + true => Ok(()), + false => Err(collect_fails), + } + }) + } + + pub fn validate_separate_u64_values(values: String) -> Result<(), String> { + validate_pipe_separated_values(values, |segment: String| { + segment + .parse::() + .map_err(|_| { + "Supply nonnegative numeric values separated by vertical bars like 111|222|333|..." + .to_string() + }) + .map(|_| ()) + }) + } + pub fn validate_private_key(key: String) -> Result<(), String> { if Regex::new("^[0-9a-fA-F]{64}$") .expect("Failed to compile regular expression") @@ -606,16 +646,24 @@ pub mod common_validators { } } - pub fn validate_separate_u64_values(values_with_delimiters: String) -> Result<(), String> { - values_with_delimiters.split('|').try_for_each(|segment| { - segment - .parse::() - .map_err(|_| { - "Supply positive numeric values separated by vertical bars like 111|222|333|..." - .to_string() - }) - .map(|_| ()) - }) + fn validate_pipe_separated_values( + values_with_delimiters: String, + closure: fn(String) -> Result<(), String>, + ) -> Result<(), String> { + let mut error_collection = vec![]; + values_with_delimiters + .split('|') + .into_iter() + .for_each(|segment| { + match closure(segment.to_string()) { + Ok(_) => (), + Err(msg) => error_collection.push(msg), + }; + }); + match error_collection.is_empty() { + true => Ok(()), + false => Err(error_collection.into_iter().collect::()), + } } } @@ -683,6 +731,7 @@ mod tests { "The Ethereum client you wish to use to provide Blockchain \ exit services from your MASQ Node (e.g. http://localhost:8545, \ https://ropsten.infura.io/v3/YOUR-PROJECT-ID, https://mainnet.infura.io/v3/YOUR-PROJECT-ID), \ + https://base-mainnet.g.alchemy.com/v2/d66UL0lPrltmweEqVsv3opBSVI3wkL8I, \ https://polygon-mainnet.infura.io/v3/YOUR-PROJECT-ID" ); assert_eq!( @@ -757,8 +806,9 @@ mod tests { on startup. A Node descriptor looks similar to one of these:\n\n\ masq://polygon-mainnet:d2U3Dv1BqtS5t_Zz3mt9_sCl7AgxUlnkB4jOMElylrU@172.50.48.6:9342\n\ masq://eth-mainnet:gBviQbjOS3e5ReFQCvIhUM3i02d1zPleo1iXg_EN6zQ@86.75.30.9:5542\n\ - masq://polygon-mumbai:A6PGHT3rRjaeFpD_rFi3qGEXAVPq7bJDfEUZpZaIyq8@14.10.50.6:10504\n\ - masq://eth-ropsten:OHsC2CAm4rmfCkaFfiynwxflUgVTJRb2oY5mWxNCQkY@150.60.42.72:6642/4789/5254\n\n\ + masq://base-mainnet:ZjPLnb9RrgsRM1D9edqH8jx9DkbPZSWqqFqLnmdKhsk@112.55.78.0:7878\n\ + masq://polygon-amoy:A6PGHT3rRjaeFpD_rFi3qGEXAVPq7bJDfEUZpZaIyq8@14.10.50.6:10504\n\ + masq://base-sepolia:OHsC2CAm4rmfCkaFfiynwxflUgVTJRb2oY5mWxNCQkY@150.60.42.72:6642/4789/5254\n\n\ Notice each of the different chain identifiers in the masq protocol prefix - they determine a family of chains \ and also the network the descriptor belongs to (mainnet or a testnet). See also the last descriptor which shows \ a configuration with multiple clandestine ports.\n\n\ @@ -925,6 +975,23 @@ mod tests { ) } + #[test] + fn validate_exit_key_fails_on_not_valid_country_code() { + let result = common_validators::validate_exit_locations(String::from("CZ|SK,RR")); + + assert_eq!( + result, + Err("'RR' is not a valid ISO3166 country code".to_string()) + ); + } + + #[test] + fn validate_exit_key_success() { + let result = common_validators::validate_exit_locations(String::from("CZ|SK")); + + assert_eq!(result, Ok(())); + } + #[test] fn validate_private_key_requires_a_key_that_is_64_characters_long() { let result = common_validators::validate_private_key(String::from("42")); @@ -1075,7 +1142,7 @@ mod tests { assert_eq!( result, Err(String::from( - "Supply positive numeric values separated by vertical bars like 111|222|333|..." + "Supply nonnegative numeric values separated by vertical bars like 111|222|333|..." )) ) } @@ -1087,7 +1154,7 @@ mod tests { assert_eq!( result, Err(String::from( - "Supply positive numeric values separated by vertical bars like 111|222|333|..." + "Supply nonnegative numeric values separated by vertical bars like 111|222|333|..." )) ) } @@ -1099,7 +1166,7 @@ mod tests { assert_eq!( result, Err(String::from( - "Supply positive numeric values separated by vertical bars like 111|222|333|..." + "Supply nonnegative numeric values separated by vertical bars like 111|222|333|..." )) ) } @@ -1144,7 +1211,7 @@ mod tests { let mut iterator = official_chain_names().iter(); assert_eq!(Chain::from(*iterator.next().unwrap()), Chain::PolyMainnet); assert_eq!(Chain::from(*iterator.next().unwrap()), Chain::EthMainnet); - assert_eq!(Chain::from(*iterator.next().unwrap()), Chain::PolyMumbai); + assert_eq!(Chain::from(*iterator.next().unwrap()), Chain::PolyAmoy); assert_eq!(Chain::from(*iterator.next().unwrap()), Chain::EthRopsten); assert_eq!(Chain::from(*iterator.next().unwrap()), Chain::Dev); assert_eq!(iterator.next(), None) diff --git a/masq_lib/src/test_utils/fake_stream_holder.rs b/masq_lib/src/test_utils/fake_stream_holder.rs index d971ffa7a..d57ed3c3c 100644 --- a/masq_lib/src/test_utils/fake_stream_holder.rs +++ b/masq_lib/src/test_utils/fake_stream_holder.rs @@ -1,136 +1,7 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::command::StdStreams; -use std::cmp::min; -use std::io; -use std::io::Read; -use std::io::Write; -use std::io::{BufRead, Error}; -use std::sync::{Arc, Mutex}; - -pub struct ByteArrayWriter { - inner_arc: Arc>, -} - -pub struct ByteArrayWriterInner { - byte_array: Vec, - next_error: Option, -} - -impl ByteArrayWriterInner { - pub fn get_bytes(&self) -> Vec { - self.byte_array.clone() - } - pub fn get_string(&self) -> String { - String::from_utf8(self.get_bytes()).unwrap() - } -} - -impl Default for ByteArrayWriter { - fn default() -> Self { - ByteArrayWriter { - inner_arc: Arc::new(Mutex::new(ByteArrayWriterInner { - byte_array: vec![], - next_error: None, - })), - } - } -} - -impl ByteArrayWriter { - pub fn new() -> ByteArrayWriter { - Self::default() - } - - pub fn inner_arc(&self) -> Arc> { - self.inner_arc.clone() - } - - pub fn get_bytes(&self) -> Vec { - self.inner_arc.lock().unwrap().byte_array.clone() - } - pub fn get_string(&self) -> String { - String::from_utf8(self.get_bytes()).unwrap() - } - - pub fn reject_next_write(&mut self, error: Error) { - self.inner_arc().lock().unwrap().next_error = Some(error); - } -} - -impl Write for ByteArrayWriter { - fn write(&mut self, buf: &[u8]) -> io::Result { - let mut inner = self.inner_arc.lock().unwrap(); - if let Some(next_error) = inner.next_error.take() { - Err(next_error) - } else { - for byte in buf { - inner.byte_array.push(*byte) - } - Ok(buf.len()) - } - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -pub struct ByteArrayReader { - byte_array: Vec, - position: usize, - next_error: Option, -} - -impl ByteArrayReader { - pub fn new(byte_array: &[u8]) -> ByteArrayReader { - ByteArrayReader { - byte_array: byte_array.to_vec(), - position: 0, - next_error: None, - } - } - - pub fn reject_next_read(mut self, error: Error) -> ByteArrayReader { - self.next_error = Some(error); - self - } -} - -impl Read for ByteArrayReader { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - match self.next_error.take() { - Some(error) => Err(error), - None => { - let to_copy = min(buf.len(), self.byte_array.len() - self.position); - #[allow(clippy::needless_range_loop)] - for idx in 0..to_copy { - buf[idx] = self.byte_array[self.position + idx] - } - self.position += to_copy; - Ok(to_copy) - } - } - } -} - -impl BufRead for ByteArrayReader { - fn fill_buf(&mut self) -> io::Result<&[u8]> { - match self.next_error.take() { - Some(error) => Err(error), - None => Ok(&self.byte_array[self.position..]), - } - } - - fn consume(&mut self, amt: usize) { - let result = self.position + amt; - self.position = if result < self.byte_array.len() { - result - } else { - self.byte_array.len() - } - } -} +use test_utilities::byte_array_reader_writer::{ByteArrayReader, ByteArrayWriter}; pub struct FakeStreamHolder { pub stdin: ByteArrayReader, diff --git a/masq_lib/src/test_utils/logging.rs b/masq_lib/src/test_utils/logging.rs index ec60ea72f..92566a753 100644 --- a/masq_lib/src/test_utils/logging.rs +++ b/masq_lib/src/test_utils/logging.rs @@ -1,6 +1,5 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::logger::real_format_function; -use crate::test_utils::fake_stream_holder::ByteArrayWriter; use crate::test_utils::utils::to_millis; use lazy_static::lazy_static; use log::set_logger; @@ -13,6 +12,7 @@ use std::sync::{Arc, Mutex, MutexGuard}; use std::thread; use std::time::Duration; use std::time::Instant; +use test_utilities::byte_array_reader_writer::ByteArrayWriter; use time::OffsetDateTime; lazy_static! { diff --git a/masq_lib/src/utils.rs b/masq_lib/src/utils.rs index 1083af2b9..8d563ef37 100644 --- a/masq_lib/src/utils.rs +++ b/masq_lib/src/utils.rs @@ -49,14 +49,14 @@ fn compute_data_directory_help() -> String { let polygon_mainnet_dir = Path::new(&data_dir.to_str().unwrap()) .join("MASQ") .join("polygon-mainnet"); - let polygon_mumbai_dir = Path::new(&data_dir.to_str().unwrap()) + let polygon_amoy_dir = Path::new(&data_dir.to_str().unwrap()) .join("MASQ") - .join("polygon-mumbai"); + .join("polygon-amoy"); format!("Directory in which the Node will store its persistent state, including at least its database \ and by default its configuration file as well. By default, your data-directory is located in \ your application directory, under your home directory e.g.: '{}'.\n\n\ In case you change your chain to a different one, the data-directory path is automatically changed \ - to end with the name of your chain: e.g.: if you choose polygon-mumbai, then data-directory is \ + to end with the name of your chain: e.g.: if you choose polygon-amoy, then data-directory is \ automatically changed to: '{}'.\n\n\ You can specify your own data-directory to the Daemon in two different ways: \n\n\ 1. If you provide a path without the chain name on the end, the Daemon will automatically change \ @@ -65,7 +65,7 @@ fn compute_data_directory_help() -> String { 2. If you provide your data directory with the corresponding chain name on the end, eg: {}/masq_home/polygon-mainnet, \ there will be no change until you set the chain parameter to a different value.", polygon_mainnet_dir.to_string_lossy().to_string().as_str(), - polygon_mumbai_dir.to_string_lossy().to_string().as_str(), + polygon_amoy_dir.to_string_lossy().to_string().as_str(), &home_dir.to_string_lossy().to_string().as_str(), &home_dir.to_string_lossy().to_string().as_str(), home_dir.to_string_lossy().to_string().as_str() diff --git a/multinode_integration_tests/src/masq_node_cluster.rs b/multinode_integration_tests/src/masq_node_cluster.rs index 86a94af54..67e7c372f 100644 --- a/multinode_integration_tests/src/masq_node_cluster.rs +++ b/multinode_integration_tests/src/masq_node_cluster.rs @@ -337,6 +337,25 @@ impl MASQNodeCluster { )), } } + + fn create_world_network(ipv4addr: Ipv4Addr, name: &str) -> Result<(), String> { + let mut command = Command::new( + "docker", + Command::strings(vec![ + "network", + "create", + format!("--subnet={}/16", ipv4addr).as_str(), + name, + ]), + ); + match command.wait_for_exit() { + 0 => Ok(()), + _ => Err(format!( + "Could not create network integration_net: {}", + command.stderr_as_string() + )), + } + } } pub struct DockerHostSocketAddr { diff --git a/multinode_integration_tests/src/neighborhood_constructor.rs b/multinode_integration_tests/src/neighborhood_constructor.rs index ba3fb650b..53a9d611e 100644 --- a/multinode_integration_tests/src/neighborhood_constructor.rs +++ b/multinode_integration_tests/src/neighborhood_constructor.rs @@ -259,8 +259,8 @@ fn from_masq_node_to_node_record(masq_node: &dyn MASQNode) -> NodeRecord { last_update: time_t_timestamp(), node_addr_opt: agr.node_addr_opt.clone(), unreachable_hosts: Default::default(), - node_distrust_score: 0, node_location_opt: None, + country_undesirability: 0u32, }, signed_gossip: agr.signed_gossip.clone(), signature: agr.signature, diff --git a/multinode_integration_tests/src/utils.rs b/multinode_integration_tests/src/utils.rs index 6c4868e89..5c522b364 100644 --- a/multinode_integration_tests/src/utils.rs +++ b/multinode_integration_tests/src/utils.rs @@ -3,7 +3,6 @@ use crate::command::Command; use crate::masq_node::{MASQNode, MASQNodeUtils}; use crate::masq_real_node::MASQRealNode; -use ip_country_lib::country_finder::COUNTRY_CODE_FINDER; use masq_lib::test_utils::utils::TEST_DEFAULT_MULTINODE_CHAIN; use masq_lib::utils::NeighborhoodModeLight; use node_lib::accountant::db_access_objects::payable_dao::{PayableDao, PayableDaoReal}; @@ -147,7 +146,7 @@ impl From<&dyn MASQNode> for AccessibleGossipRecord { signature: CryptData::new(b""), }; let ip_addr = masq_node.node_addr().ip_addr(); - let country_code = get_node_location(Some(ip_addr), &COUNTRY_CODE_FINDER); + let country_code = get_node_location(Some(ip_addr)); if let Some(cc) = country_code { agr.inner.country_code_opt = Some(cc.country_code) }; diff --git a/multinode_integration_tests/tests/data_routing_test.rs b/multinode_integration_tests/tests/data_routing_test.rs index 0b3fa9d21..0c4cef279 100644 --- a/multinode_integration_tests/tests/data_routing_test.rs +++ b/multinode_integration_tests/tests/data_routing_test.rs @@ -316,7 +316,7 @@ fn multiple_stream_zero_hop_test() { let mut another_client = zero_hop_node.make_client(8080, STANDARD_CLIENT_TIMEOUT_MILLIS); one_client.send_chunk(b"GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"); - another_client.send_chunk(b"GET /online/ HTTP/1.1\r\nHost: whatever.neverssl.com\r\n\r\n"); + another_client.send_chunk(b"GET /online/ HTTP/1.1\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Language: cs-CZ,cs;q=0.9,en;q=0.8,sk;q=0.7\r\nCache-Control: max-age=0\r\nConnection: keep-alive\r\nHost: whatever.neverssl.com\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36\r\n\r\n"); let one_response = one_client.wait_for_chunk(); let another_response = another_client.wait_for_chunk(); diff --git a/node/Cargo.lock b/node/Cargo.lock index 5d4b6605d..fabda2b8a 100644 --- a/node/Cargo.lock +++ b/node/Cargo.lock @@ -1565,7 +1565,7 @@ dependencies = [ "csv", "itertools 0.13.0", "lazy_static", - "masq_lib", + "test_utilities", ] [[package]] @@ -1855,6 +1855,7 @@ dependencies = [ "nix 0.23.1", "num", "regex", + "test_utilities", "thousands", "time 0.3.11", "websocket", @@ -1870,6 +1871,7 @@ dependencies = [ "crossbeam-channel 0.5.1", "dirs 4.0.0", "ethereum-types", + "ip_country", "itertools 0.10.3", "lazy_static", "log 0.4.18", @@ -1878,6 +1880,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "test_utilities", "time 0.3.11", "tiny-hderive", "toml", @@ -2188,6 +2191,7 @@ dependencies = [ "sodiumoxide", "sysinfo", "system-configuration", + "test_utilities", "thousands", "time 0.3.11", "tiny-bip39", @@ -3654,6 +3658,10 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "test_utilities" +version = "0.1.0" + [[package]] name = "textwrap" version = "0.11.0" diff --git a/node/Cargo.toml b/node/Cargo.toml index 345daa234..46f12ef88 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -86,6 +86,7 @@ native-tls = {version = "0.2.8", features = ["vendored"]} simple-server = "0.4.0" serial_test_derive = "0.5.1" serial_test = "0.5.1" +test_utilities = { path = "../test_utilities"} trust-dns-proto = "0.8.0" [[bin]] diff --git a/node/src/actor_system_factory.rs b/node/src/actor_system_factory.rs index 0b175a797..32b284237 100644 --- a/node/src/actor_system_factory.rs +++ b/node/src/actor_system_factory.rs @@ -1179,7 +1179,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, payment_thresholds_opt: Some(PaymentThresholds::default()), when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, @@ -1255,7 +1254,7 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), + }, payment_thresholds_opt: Default::default(), when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC @@ -1401,7 +1400,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; let make_params_arc = Arc::new(Mutex::new(vec![])); let mut subject = make_subject_with_null_setter(); @@ -1556,7 +1554,7 @@ mod tests { neighborhood_config: NeighborhoodConfig { mode: NeighborhoodMode::ConsumeOnly(vec![]), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), + }, payment_thresholds_opt: Default::default(), when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC @@ -1746,7 +1744,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, node_descriptor: Default::default(), payment_thresholds_opt: Default::default(), diff --git a/node/src/apps.rs b/node/src/apps.rs index 9aed6369b..2a38c7e47 100644 --- a/node/src/apps.rs +++ b/node/src/apps.rs @@ -6,7 +6,7 @@ use lazy_static::lazy_static; use masq_lib::constants::{HIGHEST_USABLE_PORT, LOWEST_USABLE_INSECURE_PORT}; use masq_lib::shared_schema::{ chain_arg, data_directory_arg, db_password_arg, real_user_arg, shared_app, ui_port_arg, - DB_PASSWORD_HELP, + DATA_DIRECTORY_HELP, DB_PASSWORD_HELP, }; use masq_lib::utils::DATA_DIRECTORY_DAEMON_HELP; @@ -36,7 +36,9 @@ pub fn app_daemon() -> App<'static, 'static> { } pub fn app_node() -> App<'static, 'static> { - shared_app(app_head().after_help(NODE_HELP_TEXT)).arg(ui_port_arg(&DAEMON_UI_PORT_HELP)) + shared_app(app_head().after_help(NODE_HELP_TEXT)) + .arg(data_directory_arg(DATA_DIRECTORY_HELP)) + .arg(ui_port_arg(&DAEMON_UI_PORT_HELP)) } pub fn app_config_dumper() -> App<'static, 'static> { @@ -107,9 +109,9 @@ mod tests { let polygon_mainnet_dir = Path::new(&data_dir.to_str().unwrap()) .join("MASQ") .join("polygon-mainnet"); - let polygon_mumbai_dir = Path::new(&data_dir.to_str().unwrap()) + let polygon_amoy_dir = Path::new(&data_dir.to_str().unwrap()) .join("MASQ") - .join("polygon-mumbai"); + .join("polygon-amoy"); assert_eq!( DATA_DIRECTORY_DAEMON_HELP.as_str(), @@ -117,7 +119,7 @@ mod tests { and by default its configuration file as well. By default, your data-directory is located in \ your application directory, under your home directory e.g.: '{}'.\n\n\ In case you change your chain to a different one, the data-directory path is automatically changed \ - to end with the name of your chain: e.g.: if you choose polygon-mumbai, then data-directory is \ + to end with the name of your chain: e.g.: if you choose polygon-amoy, then data-directory is \ automatically changed to: '{}'.\n\n\ You can specify your own data-directory to the Daemon in two different ways: \n\n\ 1. If you provide a path without the chain name on the end, the Daemon will automatically change \ @@ -126,7 +128,7 @@ mod tests { 2. If you provide your data directory with the corresponding chain name on the end, eg: {}/masq_home/polygon-mainnet, \ there will be no change until you set the chain parameter to a different value.", polygon_mainnet_dir.to_string_lossy().to_string().as_str(), - polygon_mumbai_dir.to_string_lossy().to_string().as_str(), + polygon_amoy_dir.to_string_lossy().to_string().as_str(), &home_dir.to_string_lossy().to_string().as_str(), &home_dir.to_string_lossy().to_string().as_str(), home_dir.to_string_lossy().to_string().as_str() diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index 93e4d9bb1..1f6e4d4be 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -1828,7 +1828,7 @@ mod tests { assert_eq!(Some(100000u64), max_block_count); } /* - POKT (Polygon mainnet and mumbai) + POKT (Polygon mainnet and amoy) {"jsonrpc":"2.0","id":7,"error":{"message":"You cannot query logs for more than 100000 blocks at once.","code":-32064}} */ /* diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs index 083f75822..c3f41f054 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs @@ -579,7 +579,7 @@ where 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, + Chain::PolyMainnet | Chain::PolyAmoy => 70_000, } } @@ -1107,7 +1107,7 @@ mod tests { #[test] fn build_of_the_blockchain_agent_fails_on_fetching_gas_price() { - let chain = Chain::PolyMumbai; + let chain = Chain::PolyAmoy; let wallet = make_wallet("abc"); let persistent_config = PersistentConfigurationMock::new().gas_price_result(Err( PersistentConfigError::UninterpretableValue("booga".to_string()), @@ -1613,10 +1613,7 @@ mod tests { Subject::web3_gas_limit_const_part(Chain::PolyMainnet), 70_000 ); - assert_eq!( - Subject::web3_gas_limit_const_part(Chain::PolyMumbai), - 70_000 - ); + assert_eq!(Subject::web3_gas_limit_const_part(Chain::PolyAmoy), 70_000); assert_eq!(Subject::web3_gas_limit_const_part(Chain::Dev), 55_000); } @@ -1683,7 +1680,7 @@ mod tests { #[test] fn signing_error_terminates_iteration_over_accounts_and_propagates_it_all_way_up_and_out() { let transport = TestTransport::default(); - let chain = Chain::PolyMumbai; + let chain = Chain::PolyAmoy; let batch_payable_tools = BatchPayableToolsMock::::default() .sign_transaction_result(Err(Web3Error::Signing( secp256k1secrets::Error::InvalidSecretKey, @@ -1749,7 +1746,7 @@ 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 chain = Chain::PolyMumbai; + let chain = Chain::PolyAmoy; let mut subject = BlockchainInterfaceWeb3::new(transport, make_fake_event_loop_handle(), chain); subject.batch_payable_tools = Box::new(batch_payable_tools); @@ -1781,7 +1778,7 @@ mod tests { secp256k1secrets::Error::InvalidSecretKey, ))); let consuming_wallet_secret_raw_bytes = b"okay-wallet"; - let chain = Chain::PolyMumbai; + let chain = Chain::PolyAmoy; let mut subject = BlockchainInterfaceWeb3::new(transport, make_fake_event_loop_handle(), chain); subject.batch_payable_tools = Box::new(batch_payable_tools); @@ -1801,10 +1798,6 @@ 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 = Bip32EncryptionKeyProvider::from_raw_secret( &decode_hex("97923d8fd8de4a00f912bfb77ef483141dec551bd73ea59343ef5c4aac965d04") @@ -1832,12 +1825,13 @@ mod tests { let recipient_wallet = test_recipient_wallet(); let nonce_correct_type = U256::from(nonce); let gas_price = match chain { - Chain::EthMainnet | Chain::EthRopsten | Chain::Dev => TEST_GAS_PRICE_ETH, - Chain::PolyMainnet | Chain::PolyMumbai => TEST_GAS_PRICE_POLYGON, + Chain::EthMainnet | Chain::EthRopsten | Chain::Dev => 110, + Chain::PolyMainnet | Chain::PolyAmoy => 55, }; + let payment_size_wei = 1_000_000_000_000; let payable_account = make_payable_account_with_wallet_and_balance_and_timestamp_opt( recipient_wallet, - TEST_PAYMENT_AMOUNT, + payment_size_wei, None, ); @@ -1855,57 +1849,63 @@ mod tests { assert_eq!(byte_set_to_compare.as_slice(), template) } - //with a real confirmation through a transaction sent with this data to the network + // Transaction with this input was verified on the test network #[test] - fn web3_interface_signing_a_transaction_works_for_polygon_mumbai() { - let chain = Chain::PolyMumbai; - let nonce = 5; - // signed_transaction_data changed after we changed the contract address of polygon matic - let signed_transaction_data = "f8ad05850ba43b740083011980949b27034acabd44223fb23d628ba4849867ce1db280b844a9059cbb0000000000000000000000007788df76bbd9a0c7c3e5bf0f77bb28c60a167a7b000000000000000000000000000000000000000000000000000000e8d4a5100083027126a09fdbbd7064d3b7240f5422b2164aaa13d62f0946a683d82ee26f97f242570d90a077b49dbb408c20d73e0666ba0a77ac888bf7a9cb14824a5f35c97217b9bc0a5a"; - + fn web3_interface_signing_a_transaction_works_for_polygon_amoy() { + let chain = Chain::PolyAmoy; + let nonce = 4; + let signed_transaction_data = "\ + f8ad04850cce4166008301198094d98c3ebd6b7f9b7cda2449ecac00d1e5f47a819380b844a9059cbb000000000\ + 0000000000000007788df76bbd9a0c7c3e5bf0f77bb28c60a167a7b000000000000000000000000000000000000\ + 000000000000000000e8d4a5100083027127a0ddd78a41c42b7a409c281292f7c6aedefab8b461d87371fe402b4\ + b0804a092f2a04b1b599ac2c1ff07bb3d40d3698c454691c3b70d99f1e5d840c852e968c96a10"; let in_bytes = decode_hex(signed_transaction_data).unwrap(); assert_that_signed_transactions_agrees_with_template(chain, nonce, &in_bytes) } - //with a real confirmation through a transaction sent with this data to the network + // Transaction with this input was verified on the test network #[test] fn web3_interface_signing_a_transaction_works_for_eth_ropsten() { let chain = Chain::EthRopsten; - let nonce = 1; //must stay like this! - let signed_transaction_data = "f8a90185199c82cc0082dee894384dec25e03f94931767ce4c3556168468ba24c380b844a9059cbb0000000000000000000000007788df76bbd9a0c7c3e5bf0f77bb28c60a167a7b000000000000000000000000000000000000000000000000000000e8d4a510002aa0635fbb3652e1c3063afac6ffdf47220e0431825015aef7daff9251694e449bfca00b2ed6d556bd030ac75291bf58817da15a891cd027a4c261bb80b51f33b78adf"; + let nonce = 1; + let signed_transaction_data = "\ + f8a90185199c82cc0082dee894384dec25e03f94931767ce4c3556168468ba24c380b844a9059cbb00000000000\ + 00000000000007788df76bbd9a0c7c3e5bf0f77bb28c60a167a7b00000000000000000000000000000000000000\ + 0000000000000000e8d4a510002aa0635fbb3652e1c3063afac6ffdf47220e0431825015aef7daff9251694e449\ + bfca00b2ed6d556bd030ac75291bf58817da15a891cd027a4c261bb80b51f33b78adf"; let in_bytes = decode_hex(signed_transaction_data).unwrap(); assert_that_signed_transactions_agrees_with_template(chain, nonce, &in_bytes) } - //not confirmed on the real network + // Unconfirmed on the real network #[test] fn web3_interface_signing_a_transaction_for_polygon_mainnet() { let chain = Chain::PolyMainnet; let nonce = 10; - //generated locally + // Generated locally let signed_transaction_data = [ - 248, 172, 10, 133, 11, 164, 59, 116, 0, 131, 1, 25, 128, 148, 238, 154, 53, 47, 106, + 248, 172, 10, 133, 12, 206, 65, 102, 0, 131, 1, 25, 128, 148, 238, 154, 53, 47, 106, 172, 74, 241, 165, 185, 244, 103, 246, 169, 62, 15, 251, 233, 221, 53, 128, 184, 68, 169, 5, 156, 187, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 119, 136, 223, 118, 187, 217, 160, 199, 195, 229, 191, 15, 119, 187, 40, 198, 10, 22, 122, 123, 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, 232, 212, 165, 16, 0, 130, - 1, 53, 160, 7, 203, 40, 44, 202, 233, 15, 5, 64, 218, 199, 239, 94, 126, 152, 2, 108, - 30, 157, 75, 124, 129, 117, 27, 109, 163, 132, 27, 11, 123, 137, 10, 160, 18, 170, 130, - 198, 73, 190, 158, 235, 0, 77, 118, 213, 244, 229, 225, 143, 156, 214, 219, 204, 193, - 155, 199, 164, 162, 31, 134, 51, 139, 130, 152, 104, + 1, 53, 160, 200, 159, 77, 202, 128, 195, 67, 122, 35, 204, 26, 65, 171, 89, 253, 82, 6, + 176, 192, 225, 41, 61, 151, 82, 66, 232, 72, 44, 68, 131, 140, 117, 160, 117, 66, 154, + 132, 183, 97, 219, 131, 214, 72, 220, 66, 152, 72, 15, 107, 44, 237, 193, 16, 193, 52, + 6, 94, 216, 149, 94, 102, 199, 80, 68, 105, ]; assert_that_signed_transactions_agrees_with_template(chain, nonce, &signed_transaction_data) } - //not confirmed on the real network + // Unconfirmed on the real network #[test] fn web3_interface_signing_a_transaction_for_eth_mainnet() { let chain = Chain::EthMainnet; let nonce = 10; - //generated locally + // Generated locally let signed_transaction_data = [ 248, 169, 10, 133, 25, 156, 130, 204, 0, 130, 222, 232, 148, 6, 243, 195, 35, 240, 35, 140, 114, 191, 53, 1, 16, 113, 242, 181, 183, 244, 58, 5, 76, 128, 184, 68, 169, 5, @@ -1921,8 +1921,8 @@ mod tests { assert_that_signed_transactions_agrees_with_template(chain, nonce, &signed_transaction_data) } - //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 + // Adapted test from old times when we had our own signing method. + // Don't have data for new chains, so I omit them in this kind of tests #[test] fn signs_various_transactions_for_eth_mainnet() { let signatures = &[ @@ -1955,8 +1955,8 @@ mod tests { assert_signature(Chain::EthMainnet, signatures) } - //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 + // Adapted test from old times when we had our own signing method. + // Don't have data for new chains, so I omit them in this kind of tests #[test] fn signs_various_transactions_for_ropsten() { let signatures = &[ diff --git a/node/src/blockchain/test_utils.rs b/node/src/blockchain/test_utils.rs index 4a8f4ecd6..16f7d21da 100644 --- a/node/src/blockchain/test_utils.rs +++ b/node/src/blockchain/test_utils.rs @@ -331,7 +331,7 @@ pub fn all_chains() -> [Chain; 4] { [ Chain::EthMainnet, Chain::PolyMainnet, - Chain::PolyMumbai, + Chain::PolyAmoy, Chain::Dev, ] } diff --git a/node/src/bootstrapper.rs b/node/src/bootstrapper.rs index f1ece6d54..fccdf83c4 100644 --- a/node/src/bootstrapper.rs +++ b/node/src/bootstrapper.rs @@ -400,7 +400,6 @@ impl BootstrapperConfig { neighborhood_config: NeighborhoodConfig { mode: NeighborhoodMode::ZeroHop, min_hops: DEFAULT_MIN_HOPS, - country: "ZZ".to_string(), }, when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, } @@ -1156,7 +1155,7 @@ mod tests { "--real-user", "123:456:/home/booga", "--chain", - "polygon-mumbai", + "polygon-amoy", ])) .unwrap(); @@ -1240,7 +1239,6 @@ mod tests { let neighborhood_config = NeighborhoodConfig { mode: NeighborhoodMode::OriginateOnly(vec![], rate_pack(9)), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; let earning_wallet = make_wallet("earning wallet"); let consuming_wallet_opt = Some(make_wallet("consuming wallet")); @@ -1861,7 +1859,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; config.data_directory = data_dir.clone(); config.clandestine_port_opt = Some(port); @@ -1932,7 +1929,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; config.data_directory = data_dir.clone(); config.clandestine_port_opt = None; @@ -1982,7 +1978,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; let listener_handler = ListenerHandlerNull::new(vec![]); let mut subject = BootstrapperBuilder::new() @@ -2020,7 +2015,6 @@ mod tests { cryptde, ))]), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; let listener_handler = ListenerHandlerNull::new(vec![]); let mut subject = BootstrapperBuilder::new() @@ -2051,7 +2045,6 @@ mod tests { config.neighborhood_config = NeighborhoodConfig { mode: NeighborhoodMode::ZeroHop, min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; let listener_handler = ListenerHandlerNull::new(vec![]); let mut subject = BootstrapperBuilder::new() @@ -2083,7 +2076,6 @@ mod tests { config.neighborhood_config = NeighborhoodConfig { mode: NeighborhoodMode::Standard(NodeAddr::default(), vec![], DEFAULT_RATE_PACK), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; let mut subject = BootstrapperBuilder::new().config(config).build(); subject.set_up_clandestine_port(); diff --git a/node/src/daemon/mod.rs b/node/src/daemon/mod.rs index 78b8491e9..f16bcbc7f 100644 --- a/node/src/daemon/mod.rs +++ b/node/src/daemon/mod.rs @@ -161,7 +161,7 @@ impl Daemon { node_process_id: None, node_ui_port: None, verifier_tools: Box::new(VerifierToolsReal::new()), - setup_reporter: Box::new(SetupReporterReal::new(Box::new(DirsWrapperReal {}))), + setup_reporter: Box::new(SetupReporterReal::new(Box::new(DirsWrapperReal::default()))), logger: Logger::new("Daemon"), } } diff --git a/node/src/daemon/setup_reporter.rs b/node/src/daemon/setup_reporter.rs index d882ee773..a8da581d5 100644 --- a/node/src/daemon/setup_reporter.rs +++ b/node/src/daemon/setup_reporter.rs @@ -26,7 +26,7 @@ use crate::sub_lib::neighborhood::NodeDescriptor; use crate::sub_lib::neighborhood::{NeighborhoodMode as NeighborhoodModeEnum, DEFAULT_RATE_PACK}; use crate::sub_lib::utils::make_new_multi_config; use crate::test_utils::main_cryptde; -use clap::value_t; +use clap::{value_t, App}; use itertools::Itertools; use masq_lib::blockchains::chains::Chain as BlockChain; use masq_lib::constants::DEFAULT_CHAIN; @@ -36,8 +36,10 @@ use masq_lib::messages::{UiSetupRequestValue, UiSetupResponseValue, UiSetupRespo 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, to_string, ExpectValue}; +use masq_lib::shared_schema::{data_directory_arg, shared_app, ConfiguratorError}; +use masq_lib::utils::{ + add_chain_specific_directory, to_string, ExpectValue, DATA_DIRECTORY_DAEMON_HELP, +}; use std::collections::HashMap; use std::fmt::Display; use std::net::{IpAddr, Ipv4Addr}; @@ -67,6 +69,10 @@ pub fn setup_cluster_from(input: Vec<(&str, &str, UiSetupResponseValueStatus)>) .collect::() } +fn daemon_shared_app() -> App<'static, 'static> { + shared_app(app_head()).arg(data_directory_arg(DATA_DIRECTORY_DAEMON_HELP.as_str())) +} + pub trait SetupReporter { fn get_modified_setup( &self, @@ -222,7 +228,7 @@ impl SetupReporterReal { } pub fn get_default_params() -> SetupCluster { - let schema = shared_app(app_head()); + let schema = daemon_shared_app(); schema .p .opts @@ -492,7 +498,7 @@ impl SetupReporterReal { environment: bool, config_file: bool, ) -> Result, ConfiguratorError> { - let app = shared_app(app_head()); + let app = daemon_shared_app(); let mut vcls: Vec> = vec![]; if let Some(command_line) = command_line_opt.clone() { vcls.push(Box::new(CommandLineVcl::new(command_line))); @@ -742,7 +748,7 @@ impl ValueRetriever for DataDirectory { } impl std::default::Default for DataDirectory { fn default() -> Self { - Self::new(&DirsWrapperReal) + Self::new(&DirsWrapperReal::default()) } } impl DataDirectory { @@ -1136,7 +1142,7 @@ impl ValueRetriever for RealUser { } impl std::default::Default for RealUser { fn default() -> Self { - Self::new(&DirsWrapperReal {}) + Self::new(&DirsWrapperReal::default()) } } impl RealUser { @@ -1224,11 +1230,13 @@ mod tests { make_persistent_config_real_with_config_dao_null, make_pre_populated_mocked_directory_wrapper, make_simplified_multi_config, }; - use crate::test_utils::{assert_string_contains, rate_pack}; + use crate::test_utils::{ + assert_string_contains, + make_node_base_dir_and_return_its_absolute_and_relative_path_to_os_home_dir, 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::blockchains::chains::Chain::PolyAmoy; use masq_lib::constants::{DEFAULT_CHAIN, DEFAULT_GAS_PRICE}; use masq_lib::messages::UiSetupResponseValueStatus::{Blank, Configured, Required, Set}; use masq_lib::test_utils::environment_guard::{ClapGuard, EnvironmentGuard}; @@ -1378,7 +1386,7 @@ mod tests { .into_iter() .map(|(name, value)| UiSetupRequestValue::new(name, value)) .collect_vec(); - let dirs_wrapper = Box::new(DirsWrapperReal); + let dirs_wrapper = Box::new(DirsWrapperReal::default()); let subject = SetupReporterReal::new(dirs_wrapper); let result = subject @@ -1436,7 +1444,7 @@ mod tests { ( "real-user", &RealUser::new(None, None, None) - .populate(&DirsWrapperReal {}) + .populate(&DirsWrapperReal::default()) .to_string(), Default, ), @@ -1496,7 +1504,7 @@ mod tests { ("scan-intervals","150|150|150",Set), ("scans", "off", Set), ]); - let dirs_wrapper = Box::new(DirsWrapperReal); + let dirs_wrapper = Box::new(DirsWrapperReal::default()); let subject = SetupReporterReal::new(dirs_wrapper); let result = subject.get_modified_setup(existing_setup, vec![]).unwrap(); @@ -1568,7 +1576,7 @@ mod tests { ].into_iter() .map (|(name, value)| UiSetupRequestValue::new(name, value)) .collect_vec(); - let dirs_wrapper = Box::new(DirsWrapperReal); + let dirs_wrapper = Box::new(DirsWrapperReal::default()); let subject = SetupReporterReal::new(dirs_wrapper); let result = subject @@ -1643,7 +1651,7 @@ mod tests { ("MASQ_SCAN_INTERVALS","133|133|111") ].into_iter() .for_each (|(name, value)| std::env::set_var (name, value)); - let dirs_wrapper = Box::new(DirsWrapperReal); + let dirs_wrapper = Box::new(DirsWrapperReal::default()); let params = vec![]; let subject = SetupReporterReal::new(dirs_wrapper); @@ -1956,7 +1964,7 @@ mod tests { ("scan-intervals", "111|111|111", Set), ("scans", "off", Set), ]); - let dirs_wrapper = Box::new(DirsWrapperReal); + let dirs_wrapper = Box::new(DirsWrapperReal::default()); let subject = SetupReporterReal::new(dirs_wrapper); let result = subject.get_modified_setup(existing_setup, params).unwrap(); @@ -2048,19 +2056,16 @@ mod tests { #[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(); + let (node_base_dir, node_base_dir_relative_to_os_home_dir) = + make_node_base_dir_and_return_its_absolute_and_relative_path_to_os_home_dir( + "setup_reporter", + "get_modified_setup_tilde_in_config_file_path", + ); + let existing_data_dir = node_base_dir.join("obsolete_data_dir"); + let new_dir_levels = PathBuf::new().join("whatever_dir").join("new_data_dir"); + let new_data_dir = node_base_dir.join(new_dir_levels.as_path()); + create_dir_all(new_data_dir.as_path()).unwrap(); + let mut config_file = File::create(new_data_dir.join("config.toml")).unwrap(); config_file .write_all(b"blockchain-service-url = \"https://www.mainnet.com\"\n") .unwrap(); @@ -2069,24 +2074,30 @@ mod tests { ("chain", DEFAULT_CHAIN.rec().literal_identifier, Default), ( "data-directory", - &data_dir.to_string_lossy().to_string(), + &existing_data_dir.to_string_lossy().to_string(), Default, ), ]); + let data_dir_referenced_from_the_home_dir = node_base_dir_relative_to_os_home_dir + .join(new_dir_levels) + .as_os_str() + .to_str() + .unwrap() + .to_string(); let incoming_setup = vec![ - ("data-directory", "~/masqhome"), - ("config-file", "~/masqhome/config.toml"), + ( + "data-directory", + &format!("~/{}", data_dir_referenced_from_the_home_dir), + ), + ( + "config-file", + &format!("~/{}/config.toml", data_dir_referenced_from_the_home_dir), + ), ] .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 dirs_wrapper = Box::new(DirsWrapperReal::default()); let subject = SetupReporterReal::new(dirs_wrapper); let result = subject @@ -2094,7 +2105,7 @@ mod tests { .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); + assert_eq!(actual_config_file_data, "https://www.mainnet.com"); } #[test] @@ -2264,14 +2275,10 @@ mod tests { let current_data_dir = base_dir .join("data_dir") .join("MASQ") - .join(BlockChain::PolyMumbai.rec().literal_identifier); //not a default + .join(BlockChain::PolyAmoy.rec().literal_identifier); //not a default let existing_setup = setup_cluster_from(vec![ ("blockchain-service-url", "", Required), - ( - "chain", - BlockChain::PolyMumbai.rec().literal_identifier, - Set, - ), + ("chain", BlockChain::PolyAmoy.rec().literal_identifier, Set), ("clandestine-port", "7788", Default), ("config-file", "config.toml", Default), ("consuming-private-key", "", Blank), @@ -2310,7 +2317,7 @@ mod tests { .get_modified_setup(existing_setup, incoming_setup) .unwrap_err(); - let expected_chain = PolyMumbai.rec().literal_identifier; + let expected_chain = PolyAmoy.rec().literal_identifier; let actual_chain = &resulting_setup_cluster.get("chain").unwrap().value; assert_eq!(actual_chain, expected_chain); let actual_data_directory = @@ -2338,7 +2345,7 @@ mod tests { ( "real-user", &crate::bootstrapper::RealUser::new(None, None, None) - .populate(&DirsWrapperReal {}) + .populate(&DirsWrapperReal::default()) .to_string(), Default, ), @@ -2347,7 +2354,7 @@ mod tests { .into_iter() .map(|(name, value)| UiSetupRequestValue::new(name, value)) .collect_vec(); - let dirs_wrapper = Box::new(DirsWrapperReal); + let dirs_wrapper = Box::new(DirsWrapperReal::default()); let subject = SetupReporterReal::new(dirs_wrapper); let _ = subject @@ -2375,7 +2382,7 @@ mod tests { ), ]); let incoming_setup = vec![UiSetupRequestValue::clear("neighbors")]; - let dirs_wrapper = Box::new(DirsWrapperReal); + let dirs_wrapper = Box::new(DirsWrapperReal::default()); let subject = SetupReporterReal::new(dirs_wrapper); let result = subject @@ -2487,7 +2494,7 @@ mod tests { let setup = setup_cluster_from(vec![]); let (real_user_opt, data_directory_opt, chain) = - SetupReporterReal::calculate_fundamentals(&DirsWrapperReal {}, &setup).unwrap(); + SetupReporterReal::calculate_fundamentals(&DirsWrapperReal::default(), &setup).unwrap(); assert_eq!( real_user_opt, @@ -2518,7 +2525,7 @@ mod tests { ]); let (real_user_opt, data_directory_opt, chain) = - SetupReporterReal::calculate_fundamentals(&DirsWrapperReal {}, &setup).unwrap(); + SetupReporterReal::calculate_fundamentals(&DirsWrapperReal::default(), &setup).unwrap(); assert_eq!( real_user_opt, @@ -2549,7 +2556,7 @@ mod tests { ]); let (real_user_opt, data_directory_opt, chain) = - SetupReporterReal::calculate_fundamentals(&DirsWrapperReal {}, &setup).unwrap(); + SetupReporterReal::calculate_fundamentals(&DirsWrapperReal::default(), &setup).unwrap(); assert_eq!( real_user_opt, @@ -2576,7 +2583,7 @@ mod tests { ]); let (real_user_opt, data_directory_opt, chain) = - SetupReporterReal::calculate_fundamentals(&DirsWrapperReal {}, &setup).unwrap(); + SetupReporterReal::calculate_fundamentals(&DirsWrapperReal::default(), &setup).unwrap(); assert_eq!( real_user_opt, @@ -2599,12 +2606,13 @@ mod tests { let setup = setup_cluster_from(vec![]); let (real_user_opt, data_directory_opt, chain) = - SetupReporterReal::calculate_fundamentals(&DirsWrapperReal {}, &setup).unwrap(); + SetupReporterReal::calculate_fundamentals(&DirsWrapperReal::default(), &setup).unwrap(); assert_eq!( real_user_opt, Some( - crate::bootstrapper::RealUser::new(None, None, None).populate(&DirsWrapperReal {}) + crate::bootstrapper::RealUser::new(None, None, None) + .populate(&DirsWrapperReal::default()) ) ); assert_eq!(data_directory_opt, None); @@ -2618,7 +2626,7 @@ mod tests { "setup_reporter", "blanking_a_parameter_with_a_default_produces_that_default", ); - let dirs_wrapper = Box::new(DirsWrapperReal); + let dirs_wrapper = Box::new(DirsWrapperReal::default()); let subject = SetupReporterReal::new(dirs_wrapper); let result = subject @@ -2690,7 +2698,7 @@ mod tests { .into_iter() .map(|uisrv| (uisrv.name.clone(), uisrv)) .collect(); - let subject = SetupReporterReal::new(Box::new(DirsWrapperReal {})); + let subject = SetupReporterReal::new(Box::new(DirsWrapperReal::default())); let result = subject .calculate_configured_setup(&setup, &data_directory) @@ -2736,7 +2744,7 @@ mod tests { .map(|uisrv| (uisrv.name.clone(), uisrv)) .collect(); - let (result, _) = SetupReporterReal::new(Box::new(DirsWrapperReal {})) + let (result, _) = SetupReporterReal::new(Box::new(DirsWrapperReal::default())) .calculate_configured_setup(&setup, &*data_directory); assert_eq!(result.get("gas-price").unwrap().value, "10".to_string()); @@ -2757,7 +2765,7 @@ mod tests { } let setup = vec![ //no config-file setting - UiSetupResponseValue::new("chain", "polygon-mumbai", Set), + UiSetupResponseValue::new("chain", "polygon-amoy", Set), UiSetupResponseValue::new("neighborhood-mode", "zero-hop", Set), UiSetupResponseValue::new("config-file", "booga/special.toml", Set), UiSetupResponseValue::new( @@ -2769,7 +2777,7 @@ mod tests { .into_iter() .map(|uisrv| (uisrv.name.clone(), uisrv)) .collect(); - let subject = SetupReporterReal::new(Box::new(DirsWrapperReal {})); + let subject = SetupReporterReal::new(Box::new(DirsWrapperReal::default())); let result = subject .calculate_configured_setup(&setup, &data_directory) .0; @@ -2795,7 +2803,7 @@ mod tests { .into_iter() .map(|uisrv| (uisrv.name.clone(), uisrv)) .collect(); - let subject = SetupReporterReal::new(Box::new(DirsWrapperReal {})); + let subject = SetupReporterReal::new(Box::new(DirsWrapperReal::default())); let result = subject .calculate_configured_setup(&setup, &data_directory) @@ -2833,7 +2841,7 @@ mod tests { .into_iter() .map(|uisrv| (uisrv.name.clone(), uisrv)) .collect(); - let subject = SetupReporterReal::new(Box::new(DirsWrapperReal {})); + let subject = SetupReporterReal::new(Box::new(DirsWrapperReal::default())); let result = subject.calculate_configured_setup(&setup, &data_dir).0; @@ -2848,7 +2856,7 @@ mod tests { ); let config_file_dir = config_file_dir.canonicalize().unwrap(); let config_file_path = config_file_dir.join("nonexistent.toml"); - let wrapper = DirsWrapperReal {}; + let wrapper = DirsWrapperReal::default(); let data_directory = wrapper .data_dir() .unwrap() @@ -2866,7 +2874,7 @@ mod tests { .into_iter() .map(|uisrv| (uisrv.name.clone(), uisrv)) .collect(); - let subject = SetupReporterReal::new(Box::new(DirsWrapperReal {})); + let subject = SetupReporterReal::new(Box::new(DirsWrapperReal::default())); let result = subject .calculate_configured_setup(&setup, &data_directory) @@ -2919,11 +2927,14 @@ mod tests { #[test] fn data_directory_computed_default() { - let real_user = RealUser::new(None, None, None).populate(&DirsWrapperReal {}); - let expected = - data_directory_from_context(&DirsWrapperReal {}, &real_user, Blockchain::EthMainnet) - .to_string_lossy() - .to_string(); + let real_user = RealUser::new(None, None, None).populate(&DirsWrapperReal::default()); + let expected = data_directory_from_context( + &DirsWrapperReal::default(), + &real_user, + Blockchain::EthMainnet, + ) + .to_string_lossy() + .to_string(); let mut config = BootstrapperConfig::new(); config.real_user = real_user; config.blockchain_bridge_config.chain = Blockchain::from("eth-mainnet"); @@ -3317,7 +3328,7 @@ mod tests { result, Some(( RealUser::new(None, None, None) - .populate(&DirsWrapperReal {}) + .populate(&DirsWrapperReal::default()) .to_string(), Default )) @@ -3699,7 +3710,7 @@ mod tests { "data-directory", &masqhome.to_str().unwrap(), )]; - let dirs_wrapper = Box::new(DirsWrapperReal); + let dirs_wrapper = Box::new(DirsWrapperReal::default()); let subject = SetupReporterReal::new(dirs_wrapper); let result = subject.get_modified_setup(existing_setup, incoming_setup); @@ -3716,10 +3727,10 @@ mod tests { let _guard = EnvironmentGuard::new(); let existing_setup = setup_cluster_from(vec![("real-user", "1111:1111:/home/booga", Default)]); - let incoming_setup = vec![UiSetupRequestValue::new("chain", "polygon-mumbai")]; + let incoming_setup = vec![UiSetupRequestValue::new("chain", "polygon-amoy")]; let home_directory = Path::new("/home/booga"); let data_directory = home_directory.join("data"); - let expected = data_directory.join("MASQ").join("polygon-mumbai"); + let expected = data_directory.join("MASQ").join("polygon-amoy"); let dirs_wrapper = Box::new( DirsWrapperMock::new() .data_dir_result(Some(data_directory)) diff --git a/node/src/database/config_dumper.rs b/node/src/database/config_dumper.rs index 891eac1ad..78f23ade7 100644 --- a/node/src/database/config_dumper.rs +++ b/node/src/database/config_dumper.rs @@ -198,7 +198,7 @@ mod tests { .opt("--dump-config") .into(); let subject = DumpConfigRunnerReal { - dirs_wrapper: Box::new(DirsWrapperReal), + dirs_wrapper: Box::new(DirsWrapperReal::default()), }; let caught_panic = catch_unwind(AssertUnwindSafe(|| { @@ -239,7 +239,7 @@ mod tests { .opt("--dump-config") .into(); let subject = DumpConfigRunnerReal { - dirs_wrapper: Box::new(DirsWrapperReal), + dirs_wrapper: Box::new(DirsWrapperReal::default()), }; let result = subject.go(&mut holder.streams(), args_vec.as_slice()); @@ -471,7 +471,7 @@ mod tests { .opt("--dump-config") .into(); let subject = DumpConfigRunnerReal { - dirs_wrapper: Box::new(DirsWrapperReal), + dirs_wrapper: Box::new(DirsWrapperReal::default()), }; let result = subject.go(&mut holder.streams(), args_vec.as_slice()); @@ -579,7 +579,7 @@ mod tests { .opt("--dump-config") .into(); let subject = DumpConfigRunnerReal { - dirs_wrapper: Box::new(DirsWrapperReal), + dirs_wrapper: Box::new(DirsWrapperReal::default()), }; let result = subject.go(&mut holder.streams(), args_vec.as_slice()); diff --git a/node/src/neighborhood/dot_graph.rs b/node/src/neighborhood/dot_graph.rs index 345afe077..09e30329c 100644 --- a/node/src/neighborhood/dot_graph.rs +++ b/node/src/neighborhood/dot_graph.rs @@ -12,6 +12,7 @@ pub struct NodeRenderableInner { pub version: u32, pub accepts_connections: bool, pub routes_data: bool, + pub country_code: String } pub struct NodeRenderable { @@ -45,10 +46,11 @@ impl NodeRenderable { fn render_label(&self) -> String { let inner_string = match &self.inner { Some(inner) => format!( - "{}{} v{}\\n", + "{}{} v{} {}\\n", if inner.accepts_connections { "A" } else { "a" }, if inner.routes_data { "R" } else { "r" }, inner.version, + inner.country_code ), None => String::new(), }; @@ -107,6 +109,7 @@ mod tests { version: 1, accepts_connections: true, routes_data: true, + country_code: "ZZ".to_string(), }), public_key: public_key.clone(), node_addr: None, @@ -120,7 +123,7 @@ mod tests { assert_string_contains( &result, &format!( - "\"{}\" [label=\"AR v1\\n{}\"];", + "\"{}\" [label=\"AR v1 ZZ\\n{}\"];", public_key_64, public_key_trunc ), ); @@ -135,6 +138,7 @@ mod tests { version: 1, accepts_connections: false, routes_data: false, + country_code: "ZZ".to_string(), }), public_key: public_key.clone(), node_addr: None, @@ -148,7 +152,7 @@ mod tests { assert_string_contains( &result, &format!( - "\"{}\" [label=\"ar v1\\n{}\"];", + "\"{}\" [label=\"ar v1 ZZ\\n{}\"];", public_key_64, public_key_64 ), ); diff --git a/node/src/neighborhood/gossip.rs b/node/src/neighborhood/gossip.rs index 1c156b4b6..389d2e933 100644 --- a/node/src/neighborhood/gossip.rs +++ b/node/src/neighborhood/gossip.rs @@ -378,11 +378,16 @@ impl Gossip_0v1 { to: k.clone(), }) }); + let country_code = match &nri.country_code_opt { + Some(cc) => cc.clone(), + None => "ZZ".to_string(), + }; node_renderables.push(NodeRenderable { inner: Some(NodeRenderableInner { version: nri.version, accepts_connections: nri.accepts_connections, routes_data: nri.routes_data, + country_code }), public_key: nri.public_key.clone(), node_addr: addr.clone(), diff --git a/node/src/neighborhood/gossip_acceptor.rs b/node/src/neighborhood/gossip_acceptor.rs index fa5009ae7..a7d4574db 100644 --- a/node/src/neighborhood/gossip_acceptor.rs +++ b/node/src/neighborhood/gossip_acceptor.rs @@ -3,7 +3,7 @@ use crate::neighborhood::gossip::{GossipBuilder, GossipNodeRecord, Gossip_0v1}; use crate::neighborhood::neighborhood_database::{NeighborhoodDatabase, NeighborhoodDatabaseError}; use crate::neighborhood::node_record::NodeRecord; -use crate::neighborhood::AccessibleGossipRecord; +use crate::neighborhood::{AccessibleGossipRecord, UserExitPreferences}; use crate::sub_lib::cryptde::{CryptDE, PublicKey}; use crate::sub_lib::neighborhood::{ ConnectionProgressEvent, ConnectionProgressMessage, GossipFailure_0v1, NeighborhoodMetadata, @@ -130,7 +130,7 @@ impl GossipHandler for DebutHandler { database: &mut NeighborhoodDatabase, mut agrs: Vec, gossip_source: SocketAddr, - _neighborhood_metadata: NeighborhoodMetadata, + neighborhood_metadata: NeighborhoodMetadata, ) -> GossipAcceptanceResult { let source_agr = { let mut agr = agrs.remove(0); // empty Gossip shouldn't get here @@ -165,7 +165,13 @@ impl GossipHandler for DebutHandler { source_node_addr, ); } - if let Ok(result) = self.try_accept_debut(cryptde, database, &source_agr, gossip_source) { + if let Ok(result) = self.try_accept_debut( + cryptde, + database, + &source_agr, + gossip_source, + neighborhood_metadata.user_exit_preferences_opt, + ) { return result; } debug!(self.logger, "Seeking neighbor for Pass"); @@ -266,13 +272,22 @@ impl DebutHandler { database: &mut NeighborhoodDatabase, debuting_agr: &AccessibleGossipRecord, gossip_source: SocketAddr, + user_exit_preferences_opt: Option, ) -> Result { if database.gossip_target_degree(database.root().public_key()) >= MAX_DEGREE { debug!(self.logger, "Neighbor count already at maximum"); return Err(()); } let debut_node_addr_opt = debuting_agr.node_addr_opt.clone(); - let debuting_node = NodeRecord::from(debuting_agr); + let mut debuting_node = NodeRecord::from(debuting_agr); + match user_exit_preferences_opt { + Some(user_exit_preferences) => { + //TODO 788 check if country code is present in Neighborhood DB and if yes, perform assign country code to exit_countries without duplication + user_exit_preferences.assign_nodes_country_undesirability(&mut debuting_node); + } + None => (), + } + // TODO 468 make debuting_node mut and add country_undesirability to its metadata let debut_node_key = database .add_node(debuting_node) .expect("Debuting Node suddenly appeared in database"); @@ -685,7 +700,13 @@ impl GossipHandler for IntroductionHandler { .as_ref() .expect("IP Address not found for the Node Addr.") .ip_addr(); - match self.update_database(database, cryptde, introducer) { + // TODO 468 pass the NeighborhoodMetadata into update_database + match self.update_database( + database, + cryptde, + introducer, + neighborhood_metadata.user_exit_preferences_opt, + ) { Ok(_) => (), Err(e) => { return GossipAcceptanceResult::Ban(format!( @@ -845,6 +866,7 @@ impl IntroductionHandler { database: &mut NeighborhoodDatabase, cryptde: &dyn CryptDE, introducer: AccessibleGossipRecord, + user_exit_preferences_opt: Option, ) -> Result { let introducer_key = introducer.inner.public_key.clone(); match database.node_by_key_mut(&introducer_key) { @@ -869,7 +891,14 @@ impl IntroductionHandler { } } None => { - let new_introducer = NodeRecord::from(introducer); + let mut new_introducer = NodeRecord::from(introducer); + //TODO 468 add country undesirability + match user_exit_preferences_opt { + //TODO 788 check if country code is present in Neighborhood DB and if yes, perform assign country code to exit_countries without duplication + Some(user_exit_preferences) => user_exit_preferences + .assign_nodes_country_undesirability(&mut new_introducer), + None => (), + } debug!( self.logger, "Adding introducer {} to database", introducer_key @@ -984,6 +1013,7 @@ impl GossipHandler for StandardGossipHandler { database, &filtered_agrs, gossip_source, + neighborhood_metadata.user_exit_preferences_opt.as_ref(), ); db_changed = self.identify_and_update_obsolete_nodes(database, filtered_agrs) || db_changed; db_changed = @@ -1095,6 +1125,7 @@ impl StandardGossipHandler { database: &mut NeighborhoodDatabase, agrs: &[AccessibleGossipRecord], gossip_source: SocketAddr, + user_exit_preferences_opt: Option<&UserExitPreferences>, ) -> bool { let all_keys = database .keys() @@ -1112,7 +1143,15 @@ impl StandardGossipHandler { } }) .for_each(|agr| { - let node_record = NodeRecord::from(agr); + let mut node_record = NodeRecord::from(agr); + // TODO modify for country undesirability in node_record (make it mut) + match user_exit_preferences_opt { + Some(user_exit_preferences) => { + //TODO 788 check if country code is present in Neighborhood DB and if yes, perform assign country code to exit_countries without duplication + user_exit_preferences.assign_nodes_country_undesirability(&mut node_record) + } + None => (), + } trace!( self.logger, "Discovered new Node {:?}: {:?}", @@ -1369,8 +1408,14 @@ mod tests { use crate::neighborhood::gossip_producer::GossipProducer; use crate::neighborhood::gossip_producer::GossipProducerReal; use crate::neighborhood::node_record::NodeRecord; + use crate::neighborhood::{ + ExitPreference, UserExitPreferences, COUNTRY_UNDESIRABILITY_FACTOR, + UNREACHABLE_COUNTRY_PENALTY, + }; use crate::sub_lib::cryptde_null::CryptDENull; - use crate::sub_lib::neighborhood::{ConnectionProgressEvent, ConnectionProgressMessage}; + use crate::sub_lib::neighborhood::{ + ConnectionProgressEvent, ConnectionProgressMessage, ExitLocation, + }; 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, @@ -1407,6 +1452,7 @@ mod tests { connection_progress_peers: vec![], cpm_recipient: make_cpm_recipient().0, db_patch_size: DB_PATCH_SIZE_FOR_TEST, + user_exit_preferences_opt: None, } } @@ -1677,6 +1723,7 @@ mod tests { dest_db.add_arbitrary_half_neighbor(root_node.public_key(), half_debuted_node.public_key()); let logger = Logger::new("Debut test"); let subject = DebutHandler::new(logger); + let neighborhood_metadata = make_default_neighborhood_metadata(); let counter_debut = subject .try_accept_debut( @@ -1684,6 +1731,7 @@ mod tests { &mut dest_db, &AccessibleGossipRecord::from(&new_debutant), SocketAddr::new(IpAddr::V4(Ipv4Addr::new(4, 5, 6, 7)), 4567), + neighborhood_metadata.user_exit_preferences_opt, ) .unwrap(); @@ -2020,6 +2068,16 @@ mod tests { let cryptde = CryptDENull::from(dest_db.root().public_key(), TEST_DEFAULT_CHAIN); let subject = IntroductionHandler::new(Logger::new("test")); let agrs: Vec = gossip.try_into().unwrap(); + let mut neighborhood_metadata = make_default_neighborhood_metadata(); + neighborhood_metadata.user_exit_preferences_opt = Some(UserExitPreferences { + exit_countries: vec!["FR".to_string()], + location_preference: ExitPreference::ExitCountryNoFallback, + locations_opt: Some(vec![ExitLocation { + country_codes: vec!["FR".to_string()], + priority: 2, + }]), + db_countries: vec!["FR".to_string()], + }); let qualifies_result = subject.qualifies(&dest_db, &agrs, gossip_source); let handle_result = subject.handle( @@ -2027,7 +2085,7 @@ mod tests { &mut dest_db, agrs.clone(), gossip_source, - make_default_neighborhood_metadata(), + neighborhood_metadata, ); assert_eq!(Qualification::Matched, qualifies_result); @@ -2047,6 +2105,7 @@ mod tests { dest_db.node_by_key_mut(&agrs[0].inner.public_key).unwrap(); let mut expected_introducer = NodeRecord::from(&agrs[0]); expected_introducer.metadata.last_update = result_introducer.metadata.last_update; + expected_introducer.metadata.country_undesirability = COUNTRY_UNDESIRABILITY_FACTOR; expected_introducer.resign(); assert_eq!(result_introducer, &expected_introducer); assert_eq!( @@ -2407,6 +2466,15 @@ mod tests { let gossip_source: SocketAddr = src_root.node_addr_opt().unwrap().into(); let (cpm_recipient, recording_arc) = make_cpm_recipient(); let mut neighborhood_metadata = make_default_neighborhood_metadata(); + neighborhood_metadata.user_exit_preferences_opt = Some(UserExitPreferences { + exit_countries: vec!["FR".to_string()], + location_preference: ExitPreference::ExitCountryWithFallback, + locations_opt: Some(vec![ExitLocation { + country_codes: vec!["FR".to_string()], + priority: 1, + }]), + db_countries: vec!["FR".to_string()], + }); neighborhood_metadata.cpm_recipient = cpm_recipient; let system = System::new("test"); @@ -2419,6 +2487,22 @@ mod tests { neighborhood_metadata, ); + assert_eq!( + dest_db + .node_by_key(node_a.public_key()) + .unwrap() + .metadata + .country_undesirability, + 0u32 + ); + assert_eq!( + dest_db + .node_by_key(node_b.public_key()) + .unwrap() + .metadata + .country_undesirability, + UNREACHABLE_COUNTRY_PENALTY + ); assert_eq!(Qualification::Matched, qualifies_result); assert_eq!(GossipAcceptanceResult::Accepted, handle_result); assert_eq!( @@ -2979,12 +3063,22 @@ mod tests { let (gossip, mut debut_node, gossip_source) = make_debut(2345, Mode::Standard); let subject = make_subject(&root_node_cryptde); let before = time_t_timestamp(); + let mut neighborhood_metadata = make_default_neighborhood_metadata(); + neighborhood_metadata.user_exit_preferences_opt = Some(UserExitPreferences { + exit_countries: vec!["CZ".to_string()], + location_preference: ExitPreference::ExitCountryWithFallback, + locations_opt: Some(vec![ExitLocation { + country_codes: vec!["CZ".to_string()], + priority: 1, + }]), + db_countries: vec!["CZ".to_string()], + }); let result = subject.handle( &mut dest_db, gossip.try_into().unwrap(), gossip_source, - make_default_neighborhood_metadata(), + neighborhood_metadata, ); let after = time_t_timestamp(); @@ -3034,6 +3128,11 @@ Length: 24 (0x18) bytes let reference_node = dest_db.node_by_key_mut(debut_node.public_key()).unwrap(); debut_node.metadata.last_update = reference_node.metadata.last_update; debut_node.resign(); + assert_eq!( + reference_node.metadata.country_undesirability, + UNREACHABLE_COUNTRY_PENALTY + ); + reference_node.metadata.country_undesirability = 0u32; assert_node_records_eq(reference_node, &debut_node, before, after); } diff --git a/node/src/neighborhood/mod.rs b/node/src/neighborhood/mod.rs index f7dc08404..6fbad2992 100644 --- a/node/src/neighborhood/mod.rs +++ b/node/src/neighborhood/mod.rs @@ -9,11 +9,6 @@ pub mod node_location; pub mod node_record; pub mod overall_connection_status; -use std::collections::HashSet; -use std::convert::TryFrom; -use std::net::{IpAddr, SocketAddr}; -use std::path::PathBuf; - use actix::Context; use actix::Handler; use actix::MessageResult; @@ -23,10 +18,18 @@ use actix::{Addr, AsyncContext}; use itertools::Itertools; use masq_lib::messages::{ FromMessageBody, ToMessageBody, UiConnectionStage, UiConnectionStatusRequest, + UiSetExitLocationRequest, UiSetExitLocationResponse, }; use masq_lib::messages::{UiConnectionStatusResponse, UiShutdownRequest}; -use masq_lib::ui_gateway::{MessageTarget, NodeFromUiMessage, NodeToUiMessage}; +use masq_lib::ui_gateway::{MessageBody, MessageTarget, NodeFromUiMessage, NodeToUiMessage}; use masq_lib::utils::{exit_process, ExpectValue, NeighborhoodModeLight}; +use std::collections::{HashMap, HashSet, VecDeque}; +use std::convert::TryFrom; +use std::fmt::Debug; +use std::net::{IpAddr, SocketAddr}; +use std::path::PathBuf; +use std::rc::Rc; +use std::string::ToString; use crate::bootstrapper::BootstrapperConfig; use crate::database::db_initializer::DbInitializationConfig; @@ -48,14 +51,14 @@ 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::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}; +use crate::sub_lib::neighborhood::{ExitLocation, UpdateNodeRecordMetadataMessage}; +use crate::sub_lib::neighborhood::{ExitLocationSet, RouteQueryResponse}; use crate::sub_lib::neighborhood::{Hops, NeighborhoodMetadata, NodeQueryResponseMetadata}; use crate::sub_lib::neighborhood::{NRMetadataChange, NodeQueryMessage}; use crate::sub_lib::neighborhood::{NeighborhoodSubs, NeighborhoodTools}; @@ -74,15 +77,84 @@ use gossip_acceptor::GossipAcceptorReal; use gossip_producer::GossipProducer; use gossip_producer::GossipProducerReal; use masq_lib::blockchains::chains::Chain; +use masq_lib::constants::EXIT_COUNTRY_ERROR; use masq_lib::crash_point::CrashPoint; use masq_lib::logger::Logger; +use masq_lib::ui_gateway::MessagePath::Conversation; use neighborhood_database::NeighborhoodDatabase; use node_record::NodeRecord; pub const CRASH_KEY: &str = "NEIGHBORHOOD"; pub const DEFAULT_MIN_HOPS: Hops = Hops::ThreeHops; pub const UNREACHABLE_HOST_PENALTY: i64 = 100_000_000; +pub const UNREACHABLE_COUNTRY_PENALTY: u32 = 100_000_000; +pub const COUNTRY_UNDESIRABILITY_FACTOR: u32 = 1_000; pub const RESPONSE_UNDESIRABILITY_FACTOR: usize = 1_000; // assumed response length is request * this +pub const ZZ_COUNTRY_CODE_STRING: &str = "ZZ"; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct ExitLocationsRoutes { + routes: Vec<(Vec, i64)>, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum ExitPreference { + Nothing, + ExitCountryWithFallback, + ExitCountryNoFallback, +} + +#[derive(Clone, Debug)] +pub struct UserExitPreferences { + exit_countries: Vec, //if we cross number of countries used in one workflow, we want to change this member to HashSet + location_preference: ExitPreference, + locations_opt: Option>, + db_countries: Vec, +} + +impl UserExitPreferences { + fn new() -> UserExitPreferences { + UserExitPreferences { + exit_countries: vec![], + location_preference: ExitPreference::Nothing, + locations_opt: None, + db_countries: vec![], + } + } + + pub fn assign_nodes_country_undesirability(&self, node_record: &mut NodeRecord) { + let country_code = node_record + .inner + .country_code_opt + .clone() + .unwrap_or_else(|| ZZ_COUNTRY_CODE_STRING.to_string()); + match &self.locations_opt { + Some(exit_locations_by_priority) => { + for exit_location in exit_locations_by_priority { + if exit_location.country_codes.contains(&country_code) + && country_code != ZZ_COUNTRY_CODE_STRING + { + node_record.metadata.country_undesirability = + Self::calculate_country_undesirability( + (exit_location.priority - 1) as u32, + ); + } + if (self.location_preference == ExitPreference::ExitCountryWithFallback + && !self.exit_countries.contains(&country_code)) + || country_code == ZZ_COUNTRY_CODE_STRING + { + node_record.metadata.country_undesirability = UNREACHABLE_COUNTRY_PENALTY; + } + } + } + None => (), + } + } + + fn calculate_country_undesirability(priority: u32) -> u32 { + COUNTRY_UNDESIRABILITY_FACTOR * priority + } +} pub struct Neighborhood { cryptde: &'static dyn CryptDE, @@ -106,6 +178,7 @@ pub struct Neighborhood { db_password_opt: Option, logger: Logger, tools: NeighborhoodTools, + user_exit_preferences: UserExitPreferences, } impl Actor for Neighborhood { @@ -117,10 +190,7 @@ impl Handler for Neighborhood { fn handle(&mut self, msg: BindMessage, ctx: &mut Self::Context) -> Self::Result { ctx.set_mailbox_capacity(NODE_MAILBOX_CAPACITY); - self.hopper_opt = Some(msg.peer_actors.hopper.from_hopper_client); - self.hopper_no_lookup_opt = Some(msg.peer_actors.hopper.from_hopper_client_no_lookup); - self.connected_signal_opt = Some(msg.peer_actors.accountant.start); - self.node_to_ui_recipient_opt = Some(msg.peer_actors.ui_gateway.node_to_ui_message_sub); + self.handle_bind_message(msg); } } @@ -388,7 +458,9 @@ impl Handler for Neighborhood { fn handle(&mut self, msg: NodeFromUiMessage, _ctx: &mut Self::Context) -> Self::Result { let client_id = msg.client_id; - if let Ok((_, context_id)) = UiConnectionStatusRequest::fmb(msg.body.clone()) { + if let Ok((message, context_id)) = UiSetExitLocationRequest::fmb(msg.body.clone()) { + self.handle_exit_location_message(message, client_id, context_id); + } else if let Ok((_, context_id)) = UiConnectionStatusRequest::fmb(msg.body.clone()) { self.handle_connection_status_message(client_id, context_id); } else if let Ok((body, _)) = UiShutdownRequest::fmb(msg.body.clone()) { self.handle_shutdown_order(client_id, body); @@ -495,6 +567,7 @@ impl Neighborhood { db_password_opt: config.db_password_opt.clone(), logger: Logger::new("Neighborhood"), tools: NeighborhoodTools::default(), + user_exit_preferences: UserExitPreferences::new(), } } @@ -793,6 +866,7 @@ impl Neighborhood { connection_progress_peers: self.overall_connection_status.get_peer_addrs(), cpm_recipient, db_patch_size: self.db_patch_size, + user_exit_preferences_opt: Some(self.user_exit_preferences.clone()), }; let acceptance_result = self.gossip_acceptor.handle( &mut self.neighborhood_database, @@ -801,8 +875,12 @@ impl Neighborhood { neighborhood_metadata, ); match acceptance_result { - GossipAcceptanceResult::Accepted => self.gossip_to_neighbors(), + GossipAcceptanceResult::Accepted => { + self.user_exit_preferences.db_countries = self.init_db_countries(); + self.gossip_to_neighbors() + } GossipAcceptanceResult::Reply(next_debut, target_key, target_node_addr) => { + self.user_exit_preferences.db_countries = self.init_db_countries(); self.handle_gossip_reply(next_debut, &target_key, &target_node_addr) } GossipAcceptanceResult::Failed(failure, target_key, target_node_addr) => { @@ -813,8 +891,9 @@ impl Neighborhood { self.handle_gossip_ignored(ignored_node_name, gossip_record_count) } GossipAcceptanceResult::Ban(reason) => { - warning!(self.logger, "Malefactor detected at {}, but malefactor bans not yet implemented; ignoring: {}", gossip_source, reason - ); + // TODO in case we introduce Ban machinery we need to reinitialize the db_countries here as well - in that case, we need to make new process to subtract + // result in init_db_countries guts by one for particular country + warning!(self.logger, "Malefactor detected at {}, but malefactor bans not yet implemented; ignoring: {}", gossip_source, reason); self.handle_gossip_ignored(ignored_node_name, gossip_record_count); } } @@ -888,6 +967,7 @@ impl Neighborhood { return_component_opt: Some(Component::ProxyServer), payload_size: 10000, hostname_opt: None, + target_country_opt: None, }; if self.handle_route_query_message(msg).is_some() { debug!( @@ -1120,7 +1200,10 @@ impl Neighborhood { minimum_hop_count, origin, target_component, target_str )) } - Some(route) => Ok(RouteSegment::new(route, target_component)), + Some(route) => { + let route_ref: Vec<&PublicKey> = route.iter().collect(); + Ok(RouteSegment::new(route_ref, target_component)) + }, } } @@ -1192,11 +1275,10 @@ impl Neighborhood { fn validate_last_node_not_too_close_to_first_node( &self, - prefix_len: usize, first_node_key: &PublicKey, candidate_node_key: &PublicKey, ) -> bool { - if prefix_len <= 2 { + if self.min_hops as usize <= 1usize { true // Zero- and single-hop routes are not subject to exit-too-close restrictions } else { !self @@ -1205,6 +1287,52 @@ impl Neighborhood { } } + fn validate_fallback_country_exit_codes(&self, last_node_cc_opt: Option<&String>) -> bool { + let last_cc = match last_node_cc_opt { + Some(cc) => cc.to_owned(), + None => "ZZ".to_string(), + }; + if self.user_exit_preferences.exit_countries.contains(&last_cc) { + return true; + } + if self.user_exit_preferences.exit_countries.is_empty() { + return true; + } + for country in &self.user_exit_preferences.exit_countries { + if country == &last_cc { + return true; + } + if self.user_exit_preferences.db_countries.contains(country) && country != &last_cc { + return false; + } + } + return true; + } + + fn validate_last_node_country_code( + &self, + last_node: &NodeRecord, + research_neighborhood: bool, + direction: RouteDirection, + ) -> bool { + if self.user_exit_preferences.location_preference == ExitPreference::Nothing + || (self.user_exit_preferences.location_preference == ExitPreference::ExitCountryWithFallback + && self.validate_fallback_country_exit_codes(last_node.inner.country_code_opt.as_ref())) + || research_neighborhood + || direction == RouteDirection::Back + { + true // Zero- and single-hop routes are not subject to exit-too-close restrictions + } else { + match last_node.inner.country_code_opt.as_ref() { + Some(country_code) => self + .user_exit_preferences + .exit_countries + .contains(&country_code), + _ => false, + } + } + } + fn compute_undesirability( node_record: &NodeRecord, payload_size: u64, @@ -1215,6 +1343,7 @@ impl Neighborhood { UndesirabilityType::Relay => node_record.inner.rate_pack.routing_charge(payload_size), UndesirabilityType::ExitRequest(_) => { node_record.inner.rate_pack.exit_charge(payload_size) + + node_record.metadata.country_undesirability as u64 } UndesirabilityType::ExitAndRouteResponse => { node_record.inner.rate_pack.exit_charge(payload_size) @@ -1235,7 +1364,6 @@ impl Neighborhood { rate_undesirability += UNREACHABLE_HOST_PENALTY; } } - rate_undesirability } @@ -1259,6 +1387,94 @@ impl Neighborhood { return_route_id } + pub fn find_exit_location<'a>( + &'a self, + source: &'a PublicKey, + minimum_hops: usize, + payload_size: usize, + ) -> (HashMap, Vec) { + let mut minimum_undesirability = i64::MAX; + let initial_undesirability = 0; + let mut research_exits: Vec> = vec![Rc::new(RouteElement { + previous: None, + public_key: source.clone(), + undesirability: initial_undesirability, + })]; + let prefix = Rc::new(RouteElement { + previous: None, + public_key: source.clone(), + undesirability: initial_undesirability, + }); + let over_routes = self.routing_engine( + source, + prefix, + None, + minimum_hops, + payload_size, + RouteDirection::Over, + &mut minimum_undesirability, + None, + true, + &mut research_exits, + ); + let mut result_exit: HashMap = HashMap::new(); + let mut research_exit: Vec = vec![]; + for exit_node in over_routes { + let mut segment = VecDeque::from(vec![]); + let key = PublicKey::new(&[]); + let undesirability = exit_node.undesirability; + let mut current = Some(Rc::clone(&exit_node)); + while let Some(node) = current { + segment.push_front(node.public_key.clone()); + current = node.previous.as_ref().map(Rc::clone); + } + result_exit + .entry(key) + .and_modify(|e| { + e.routes + .push((Vec::from(segment.clone()), undesirability)) + }) + .or_insert(ExitLocationsRoutes { + routes: vec![(Vec::from(segment.clone()), undesirability)], + }); + } + for exit_node in &research_exits { + let mut segment = VecDeque::from(vec![]); + let undesirability = exit_node.undesirability; + let mut current = Some(Rc::clone(&exit_node)); + while let Some(node) = current { + segment.push_front(node.public_key.clone()); + current = node.previous.as_ref().map(Rc::clone); + } + research_exit.push(exit_node.public_key.clone()); + result_exit + .entry(exit_node.public_key.clone()) + .and_modify(|e| { + e.routes + .push((Vec::from(segment.clone()), undesirability)) + }) + .or_insert(ExitLocationsRoutes { + routes: vec![(Vec::from(segment.clone()), undesirability)], + }); + } + /* + over_routes.into_iter().for_each(|segment| { + if !segment.nodes.is_empty() { + let exit_node = segment.nodes[segment.nodes.len() - 1]; + result_exit + .entry(exit_node.clone()) + .and_modify(|e| { + e.routes + .push((segment.nodes.clone(), segment.undesirability)) + }) + .or_insert(ExitLocationsRoutes { + routes: vec![(segment.nodes.clone(), segment.undesirability)], + }); + } + });*/ + (result_exit, research_exit) + } + // Interface to main routing engine. Supply source key, target key--if any--in target_opt, // minimum hops, size of payload in bytes, the route direction, and the hostname if you know it. // @@ -1266,6 +1482,7 @@ impl Neighborhood { // target in hops_remaining or more hops with no cycles, or from the origin hops_remaining hops // out into the MASQ Network. No round trips; if you want a round trip, call this method twice. // If the return value is None, no qualifying route was found. + #[allow(clippy::too_many_arguments)] fn find_best_route_segment<'a>( &'a self, source: &'a PublicKey, @@ -1274,110 +1491,225 @@ impl Neighborhood { payload_size: usize, direction: RouteDirection, hostname_opt: Option<&str>, - ) -> Option> { + ) -> Option> { let mut minimum_undesirability = i64::MAX; let initial_undesirability = self.compute_initial_undesirability(source, payload_size as u64, direction); + let prefix = Rc::new(RouteElement { + previous: None, + public_key: source.clone(), + undesirability: initial_undesirability, + }); let result = self .routing_engine( - vec![source], - initial_undesirability, + source, + prefix, target_opt, minimum_hops, payload_size, direction, &mut minimum_undesirability, hostname_opt, + false, + &mut vec![], ) .into_iter() - .filter_map(|cr| match cr.undesirability <= minimum_undesirability { - true => Some(cr.nodes), - false => None, - }) + .filter(|cr| cr.undesirability <= minimum_undesirability) .next(); - result + //TODO reverse the public keys from Rc list + match result { + Some(route_node) => { + let mut segment = VecDeque::from(vec![]); + let mut current = Some(Rc::clone(&route_node)); + while let Some(node) = current { + segment.push_front(node.public_key.clone()); + current = node.previous.as_ref().map(Rc::clone); + } + Some(Vec::from(segment)) + }, + None => None + } + } #[allow(clippy::too_many_arguments)] fn routing_engine<'a>( &'a self, - prefix: Vec<&'a PublicKey>, - undesirability: i64, + first_node_key: &PublicKey, + prefix: Rc, target_opt: Option<&'a PublicKey>, hops_remaining: usize, payload_size: usize, direction: RouteDirection, minimum_undesirability: &mut i64, hostname_opt: Option<&str>, - ) -> Vec> { - if undesirability > *minimum_undesirability { + research_neighborhood: bool, + research_exits: &mut Vec>, + ) -> Vec> { + if prefix.undesirability > *minimum_undesirability && !research_neighborhood { return vec![]; } - let first_node_key = prefix.first().expect("Empty prefix"); + //TODO when target node is present ignore all country_codes selection - write test for route back and ignore the country code let previous_node = self .neighborhood_database - .node_by_key(prefix.last().expect("Empty prefix")) + .node_by_key(&prefix.public_key) .expect("Last Node magically disappeared"); // Check to see if we're done. If we are, all three of these qualifications will pass. if self.route_length_qualifies(hops_remaining) && self.last_key_qualifies(previous_node, target_opt) && self.validate_last_node_not_too_close_to_first_node( - prefix.len(), - *first_node_key, + first_node_key, previous_node.public_key(), ) { - if undesirability < *minimum_undesirability { - *minimum_undesirability = undesirability; + if !research_neighborhood && self.validate_last_node_country_code( + &previous_node, + research_neighborhood, + direction + ) { + if prefix.undesirability < *minimum_undesirability { + *minimum_undesirability = prefix.undesirability; + } + vec![prefix.clone()] + } else if research_neighborhood && Self::research_exit_contains(&research_exits, &prefix.public_key) { + vec![] + } else { + if research_neighborhood { + research_exits.push(Rc::new(RouteElement { + previous: Some(Rc::clone(&prefix)), + public_key: prefix.public_key.clone(), + undesirability: prefix.undesirability, + }) + ); + } + self.routing_guts( + first_node_key, + prefix, + target_opt, + hops_remaining, + payload_size, + direction, + minimum_undesirability, + hostname_opt, + research_neighborhood, + research_exits, + previous_node, + ) } - vec![ComputedRouteSegment::new(prefix, undesirability)] - } else if (hops_remaining == 0) && target_opt.is_none() { + } else if ((hops_remaining == 0) && target_opt.is_none() && !research_neighborhood) + && (self.user_exit_preferences.location_preference == ExitPreference::Nothing + || self.user_exit_preferences.exit_countries.is_empty()) + { + // in case we do not investigate neighborhood for country codes, or we do not looking for particular country exit: // don't continue a targetless search past the minimum hop count vec![] } else { - // Go through all the neighbors and compute shorter routes through all the ones we're not already using. - previous_node - .full_neighbors(&self.neighborhood_database) - .iter() - .filter(|node_record| !prefix.contains(&node_record.public_key())) - .filter(|node_record| { - node_record.routes_data() - || Self::is_orig_node_on_back_leg(**node_record, target_opt, direction) - }) - .flat_map(|node_record| { - let mut new_prefix = prefix.clone(); - new_prefix.push(node_record.public_key()); - - let new_hops_remaining = if hops_remaining == 0 { - 0 - } else { - hops_remaining - 1 - }; - - let new_undesirability = self.compute_new_undesirability( - node_record, - undesirability, - target_opt, - new_hops_remaining, - payload_size as u64, - direction, - hostname_opt, - ); + self.routing_guts( + first_node_key, + prefix, + target_opt, + hops_remaining, + payload_size, + direction, + minimum_undesirability, + hostname_opt, + research_neighborhood, + research_exits, + previous_node, + ) + } + } - self.routing_engine( - new_prefix.clone(), - new_undesirability, - target_opt, - new_hops_remaining, - payload_size, - direction, - minimum_undesirability, - hostname_opt, - ) - }) - .collect() + //TODO testdrive me + fn research_exit_contains(research_exits: &Vec>, current_key: &PublicKey) -> bool { + for prefix in research_exits { + let mut current = Some(Rc::clone(&prefix)); + while let Some(node) = current { + if *current_key == node.public_key { + return true; + } + current = node.previous.as_ref().map(Rc::clone); + } + } + false + } + + //TODO testdrive me + fn prefix_contains(prefix: &Rc, node_key_searched: &PublicKey) -> bool { + let mut current = Some(Rc::clone(&prefix)); + while let Some(node) = current { + if node_key_searched == &node.public_key { + return true; + } + current = node.previous.as_ref().map(Rc::clone); } + false + } + + #[allow(clippy::too_many_arguments)] + fn routing_guts<'a>( + &'a self, + first_node_key: &PublicKey, + prefix: Rc, + target_opt: Option<&'a PublicKey>, + hops_remaining: usize, + payload_size: usize, + direction: RouteDirection, + minimum_undesirability: &mut i64, + hostname_opt: Option<&str>, + research_neighborhood: bool, + research_exits: &mut Vec>, + previous_node: &NodeRecord, + ) -> Vec> { + // Go through all the neighbors and compute shorter routes through all the ones we're not already using. + let neighbor_nodes = previous_node.full_neighbors(&self.neighborhood_database); + let mut result_vector = Vec::with_capacity(neighbor_nodes.len()); + + let result_iter = neighbor_nodes + .iter() + .filter(|node_record| !Self::prefix_contains(&prefix, node_record.public_key())) //TODO make method to travers the elements + .filter(|node_record| { + node_record.routes_data() + || Self::is_orig_node_on_back_leg(**node_record, target_opt, direction) + }) + .flat_map(|node_record| { + let new_hops_remaining = if hops_remaining == 0 { + 0 + } else { + hops_remaining - 1 + }; + + let new_undesirability = self.compute_new_undesirability( + node_record, + prefix.undesirability, + target_opt, + new_hops_remaining, + payload_size as u64, + direction, + hostname_opt, + ); + let new_prefix = Rc::new(RouteElement { + previous: Some(Rc::clone(&prefix)), + public_key: node_record.public_key().clone(), + undesirability: new_undesirability, + }); + + self.routing_engine( + first_node_key, + new_prefix, + target_opt, + new_hops_remaining, + payload_size, + direction, + minimum_undesirability, + hostname_opt, + research_neighborhood, + research_exits, + ) + }); + result_vector.extend(result_iter); + result_vector } fn send_ask_about_debut_gossip_message( @@ -1448,6 +1780,211 @@ impl Neighborhood { undesirability + node_undesirability } + fn handle_exit_location_message( + &mut self, + message: UiSetExitLocationRequest, + client_id: u64, + context_id: u64, + ) { + let (exit_locations_by_priority, missing_locations) = + self.extract_exit_locations_from_message(&message); + self.user_exit_preferences.location_preference = match ( + message.fallback_routing, + exit_locations_by_priority.is_empty(), + ) { + (true, true) => ExitPreference::Nothing, + (true, false) => ExitPreference::ExitCountryWithFallback, + (false, false) => ExitPreference::ExitCountryNoFallback, + (false, true) => ExitPreference::Nothing, + }; + let fallback_status = match self.user_exit_preferences.location_preference { + ExitPreference::Nothing => "Fallback Routing is set.", + ExitPreference::ExitCountryWithFallback => "Fallback Routing is set.", + ExitPreference::ExitCountryNoFallback => "Fallback Routing NOT set.", + }; + self.set_exit_locations_opt(&exit_locations_by_priority); + match self.neighborhood_database.keys().len() > 1 { + true => { + self.set_country_undesirability_and_exit_countries(&exit_locations_by_priority); + let location_set = ExitLocationSet { + locations: exit_locations_by_priority, + }; + let exit_location_status = match location_set.locations.is_empty() { + false => "Exit location set: ", + true => "Exit location unset.", + }; + info!( + self.logger, + "{} {}{}", fallback_status, exit_location_status, location_set + ); + if !missing_locations.is_empty() { + warning!( + self.logger, + "Exit Location: following desired countries are missing in Neighborhood {:?}", &missing_locations + ); + } + } + false => info!( + self.logger, + "Neighborhood is empty, no exit Nodes are available.", + ), + } + let message = self.create_exit_location_response( + client_id, + context_id, + missing_locations, + message.show_countries, + ); + self.node_to_ui_recipient_opt + .as_ref() + .expect("UI Gateway is unbound") + .try_send(message) + .expect("UiGateway is dead"); + } + + fn create_exit_location_response( + &self, + client_id: u64, + context_id: u64, + missing_locations: Vec, + show_countries: bool, + ) -> NodeToUiMessage { + let errmessage = match show_countries { + true => match &missing_locations.is_empty() { + true => { + format!( + "Exit Countries: {:?}", + self.user_exit_preferences.db_countries + ) + } + false => { + format!( + "Exit Countries: {:?}\nExit Location: following desired countries are missing in Neighborhood {:?}", + self.user_exit_preferences.db_countries, + missing_locations + ) + } + }, + false => match &missing_locations.is_empty() { + true => "".to_string(), + false => { + format!( + "Exit Location: following desired countries are missing in Neighborhood {:?}", + missing_locations + ) + } + }, + }; + match &errmessage.is_empty() { + false => NodeToUiMessage { + target: MessageTarget::ClientId(client_id), + body: MessageBody { + opcode: "exit_location".to_string(), + path: Conversation(context_id), + payload: Err((EXIT_COUNTRY_ERROR, errmessage)), + }, + }, + true => NodeToUiMessage { + target: MessageTarget::ClientId(client_id), + body: UiSetExitLocationResponse {}.tmb(context_id), + }, + } + } + + fn set_exit_locations_opt(&mut self, exit_locations_by_priority: &[ExitLocation]) { + self.user_exit_preferences.locations_opt = + match self.user_exit_preferences.exit_countries.is_empty() { + false => Some(exit_locations_by_priority.to_owned()), + true => match self.user_exit_preferences.location_preference { + ExitPreference::ExitCountryNoFallback => None, + _ => Some(exit_locations_by_priority.to_owned()), + }, + }; + } + + fn set_country_undesirability_and_exit_countries( + &mut self, + exit_locations_by_priority: &Vec, + ) { + let nodes = self.neighborhood_database.nodes_mut(); + match !&exit_locations_by_priority.is_empty() { + true => { + for node_record in nodes { + self.user_exit_preferences + .assign_nodes_country_undesirability(node_record) + } + } + false => { + self.user_exit_preferences.exit_countries = vec![]; + for node_record in nodes { + node_record.metadata.country_undesirability = 0u32; + } + } + } + } + + fn extract_exit_locations_from_message( + &mut self, + message: &UiSetExitLocationRequest, + ) -> (Vec, Vec) { + self.user_exit_preferences.db_countries = self.init_db_countries(); + let mut countries_lack_in_neighborhood = vec![]; + ( + message + .to_owned() + .exit_locations + .into_iter() + .map(|cc| { + for code in &cc.country_codes { + if self.user_exit_preferences.db_countries.contains(code) + || self.user_exit_preferences.location_preference + == ExitPreference::ExitCountryWithFallback + { + self.user_exit_preferences.exit_countries.push(code.clone()); + if self.user_exit_preferences.location_preference + == ExitPreference::ExitCountryWithFallback + { + countries_lack_in_neighborhood.push(code.clone()); + } + } else { + countries_lack_in_neighborhood.push(code.clone()); + } + } + ExitLocation { + country_codes: cc.country_codes, + priority: cc.priority, + } + }) + .collect(), + countries_lack_in_neighborhood, + ) + } + + fn init_db_countries(&mut self) -> Vec { + let root_key = self.neighborhood_database.root_key(); + let min_hops = self.min_hops as usize; + let (exit_locations_db, exit_nodes) = self + .find_exit_location(root_key, min_hops, 0usize) + .to_owned(); + let mut db_countries = vec![]; + match (exit_locations_db.is_empty(), exit_nodes.is_empty()) { + (true, true) => {} + _ => { + for pub_key in exit_nodes { + let node_opt = self.neighborhood_database.node_by_key(&pub_key); + if let Some(node_record) = node_opt { + if let Some(cc) = &node_record.inner.country_code_opt { + db_countries.push(cc.clone()) + } + } + } + } + } + db_countries.sort(); + db_countries.dedup(); + db_countries + } + fn handle_gossip_reply( &self, gossip: Gossip_0v1, @@ -1610,6 +2147,13 @@ impl Neighborhood { 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); } + fn handle_bind_message(&mut self, msg: BindMessage) { + self.hopper_opt = Some(msg.peer_actors.hopper.from_hopper_client); + self.hopper_no_lookup_opt = Some(msg.peer_actors.hopper.from_hopper_client_no_lookup); + self.connected_signal_opt = Some(msg.peer_actors.accountant.start); + self.node_to_ui_recipient_opt = Some(msg.peer_actors.ui_gateway.node_to_ui_message_sub); + } + // GH-728 fn handle_new_password(&mut self, new_password: String) { self.db_password_opt = Some(new_password); @@ -1636,20 +2180,12 @@ enum UndesirabilityType<'hostname> { ExitAndRouteResponse, } -struct ComputedRouteSegment<'a> { - pub nodes: Vec<&'a PublicKey>, +struct RouteElement { + pub previous: Option>, + pub public_key: PublicKey, pub undesirability: i64, } -impl<'a> ComputedRouteSegment<'a> { - pub fn new(nodes: Vec<&'a PublicKey>, undesirability: i64) -> Self { - Self { - nodes, - undesirability, - } - } -} - #[cfg(test)] mod tests { use actix::Recipient; @@ -1669,7 +2205,9 @@ mod tests { use tokio::prelude::Future; use masq_lib::constants::{DEFAULT_CHAIN, TLS_PORT}; - use masq_lib::messages::{ToMessageBody, UiConnectionChangeBroadcast, UiConnectionStage}; + use masq_lib::messages::{ + CountryCodes, ToMessageBody, UiConnectionChangeBroadcast, UiConnectionStage, + }; use masq_lib::test_utils::utils::{ensure_node_home_directory_exists, TEST_DEFAULT_CHAIN}; use masq_lib::ui_gateway::MessageBody; use masq_lib::ui_gateway::MessagePath::Conversation; @@ -1728,6 +2266,13 @@ mod tests { }; use crate::test_utils::unshared_test_utils::notify_handlers::NotifyLaterHandleMock; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + use masq_lib::ui_gateway::MessageTarget::ClientId; + + impl NeighborhoodDatabase { + pub fn set_root_key(&mut self, key: &PublicKey) { + self.this_node = key.clone(); + } + } impl Handler> for Neighborhood { type Result = (); @@ -1755,12 +2300,7 @@ mod tests { vec![make_node_descriptor(make_ip(2))], rate_pack(100), ); - let country = "ZZ".to_string(); - let neighborhood_config = NeighborhoodConfig { - mode, - min_hops, - country, - }; + let neighborhood_config = NeighborhoodConfig { mode, min_hops }; let subject = Neighborhood::new( main_cryptde(), @@ -1777,6 +2317,60 @@ mod tests { assert_eq!(subject.db_patch_size, expected_db_patch_size); } + // #[test] + // fn test_wtih_debut_allways_have_half_neighborship_in_handle_gossip() { + // let mut subject = make_standard_subject(); + // subject.min_hops = Hops::OneHop; + // let root_node_key = subject.neighborhood_database.root_key(); + // let first_neighbor = make_node_record(1111, true); + // let mut debut_db = subject.neighborhood_database.clone(); + // debut_db.add_node(first_neighbor.clone()).unwrap(); + // debut_db.this_node = first_neighbor.public_key().clone(); + // debut_db.remove_node(&root_node_key.clone()); + // let resinging = debut_db.node_by_key_mut(first_neighbor.public_key()).unwrap(); + // resinging.resign(); + // let debut = GossipBuilder::new(&debut_db).node(first_neighbor.public_key(), true).build(); + // + // let peer_actors = peer_actors_builder().build(); + // subject.handle_bind_message(BindMessage { peer_actors }); + // + // + // subject.handle_gossip( + // debut, + // SocketAddr::from_str("1.1.1.1:1111").unwrap(), + // make_cpm_recipient().0, + // ); + // + // print!("db after: {:?}\n", subject.neighborhood_database.to_dot_graph()); + // println!("db_countries: {:?}", subject.user_exit_preferences.db_countries); + // } + + #[test] + fn init_db_countries_works_properly() { + let mut subject = make_standard_subject(); + subject.min_hops = Hops::OneHop; + let root_node = subject.neighborhood_database.root().clone(); + let mut first_neighbor = make_node_record(1111, true); + first_neighbor.inner.country_code_opt = Some("CZ".to_string()); + subject + .neighborhood_database + .add_node(first_neighbor.clone()) + .unwrap(); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(root_node.public_key(), first_neighbor.public_key()); + + let filled_db_countries = subject.init_db_countries(); + + subject + .neighborhood_database + .remove_arbitrary_half_neighbor(root_node.public_key(), first_neighbor.public_key()); + let emptied_db_countries = subject.init_db_countries(); + + assert_eq!(filled_db_countries, &["CZ".to_string()]); + assert!(emptied_db_countries.is_empty()); + } + #[test] #[should_panic( expected = "Neighbor masq://eth-ropsten:AQIDBA@1.2.3.4:1234 is not on the mainnet blockchain" @@ -1792,7 +2386,6 @@ mod tests { )) .unwrap()]), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, earning_wallet.clone(), None, @@ -1818,7 +2411,6 @@ mod tests { )) .unwrap()]), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, earning_wallet.clone(), None, @@ -1840,7 +2432,6 @@ mod tests { NeighborhoodConfig { mode: NeighborhoodMode::ZeroHop, min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, earning_wallet.clone(), None, @@ -1870,7 +2461,6 @@ mod tests { DEFAULT_RATE_PACK.clone(), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, earning_wallet.clone(), None, @@ -1910,7 +2500,6 @@ mod tests { NeighborhoodConfig { mode: NeighborhoodMode::ZeroHop, min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, earning_wallet.clone(), consuming_wallet.clone(), @@ -1963,7 +2552,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, earning_wallet.clone(), consuming_wallet.clone(), @@ -2044,7 +2632,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; let bootstrap_config = bc_from_nc_plus(neighborhood_config, make_wallet("earning"), None, "test"); @@ -2514,7 +3101,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; let mut subject = Neighborhood::new( main_cryptde(), @@ -2592,7 +3178,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, earning_wallet.clone(), None, @@ -2636,7 +3221,9 @@ mod tests { let addr: Addr = subject.start(); let sub: Recipient = addr.recipient::(); - let future = sub.send(RouteQueryMessage::data_indefinite_route_request(None, 400)); + let future = sub.send(RouteQueryMessage::data_indefinite_route_request( + None, None, 400, + )); System::current().stop_with_code(0); system.run(); @@ -2652,8 +3239,10 @@ mod tests { let addr: Addr = subject.start(); let sub: Recipient = addr.recipient::(); - let future = sub.send(RouteQueryMessage::data_indefinite_route_request(None, 430)); - + let future = sub.send(RouteQueryMessage::data_indefinite_route_request( + None, None, 430, + )); + System::current().stop_with_code(0); system.run(); let result = future.wait().unwrap(); @@ -2692,7 +3281,7 @@ mod tests { } let addr: Addr = subject.start(); let sub: Recipient = addr.recipient::(); - let msg = RouteQueryMessage::data_indefinite_route_request(None, 54000); + let msg = RouteQueryMessage::data_indefinite_route_request(None, None, 54000); let future = sub.send(msg); @@ -2752,7 +3341,7 @@ mod tests { subject.min_hops = Hops::TwoHops; let addr: Addr = subject.start(); let sub: Recipient = addr.recipient::(); - let msg = RouteQueryMessage::data_indefinite_route_request(None, 20000); + let msg = RouteQueryMessage::data_indefinite_route_request(None, None, 20000); let future = sub.send(msg); @@ -2772,7 +3361,7 @@ mod tests { let sub: Recipient = addr.recipient::(); let future = sub.send(RouteQueryMessage::data_indefinite_route_request( - None, 12345, + None, None, 12345, )); System::current().stop_with_code(0); @@ -2868,7 +3457,9 @@ mod tests { let addr: Addr = subject.start(); let sub: Recipient = addr.recipient::(); - let data_route = sub.send(RouteQueryMessage::data_indefinite_route_request(None, 5000)); + let data_route = sub.send(RouteQueryMessage::data_indefinite_route_request( + None, None, 5000, + )); System::current().stop_with_code(0); system.run(); @@ -2963,8 +3554,12 @@ mod tests { let addr: Addr = subject.start(); let sub: Recipient = addr.recipient::(); - let data_route_0 = sub.send(RouteQueryMessage::data_indefinite_route_request(None, 2000)); - let data_route_1 = sub.send(RouteQueryMessage::data_indefinite_route_request(None, 3000)); + let data_route_0 = sub.send(RouteQueryMessage::data_indefinite_route_request( + None, None, 2000, + )); + let data_route_1 = sub.send(RouteQueryMessage::data_indefinite_route_request( + None, None, 3000, + )); System::current().stop_with_code(0); system.run(); @@ -3014,15 +3609,17 @@ mod tests { ) .unwrap(); - let route_request_1 = - route_sub.send(RouteQueryMessage::data_indefinite_route_request(None, 1000)); + let route_request_1 = route_sub.send(RouteQueryMessage::data_indefinite_route_request( + None, None, 1000, + )); 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)); + let route_request_2 = route_sub.send(RouteQueryMessage::data_indefinite_route_request( + None, None, 2000, + )); System::current().stop(); system.run(); @@ -3064,6 +3661,584 @@ mod tests { )); } + #[test] + fn exit_location_with_multiple_countries_and_priorities_can_be_changed_using_exit_location_msg() + { + init_test_logging(); + let test_name = "exit_location_with_multiple_countries_and_priorities_can_be_changed_using_exit_location_msg"; + let request = UiSetExitLocationRequest { + fallback_routing: true, + exit_locations: vec![ + CountryCodes { + country_codes: vec!["CZ".to_string(), "SK".to_string()], + priority: 1, + }, + CountryCodes { + country_codes: vec!["AT".to_string(), "DE".to_string()], + priority: 2, + }, + CountryCodes { + country_codes: vec!["PL".to_string()], + priority: 3, + }, + ], + show_countries: false, + }; + let message = NodeFromUiMessage { + client_id: 0, + body: request.tmb(0), + }; + let system = System::new(test_name); + let (ui_gateway, _, _) = make_recorder(); + let mut subject = make_standard_subject(); + subject.logger = Logger::new(test_name); + let cz = &mut make_node_record(3456, true); + cz.inner.country_code_opt = Some("CZ".to_string()); + let us = &mut make_node_record(4567, true); + us.inner.country_code_opt = Some("US".to_string()); + let sk = &mut make_node_record(5678, true); + sk.inner.country_code_opt = Some("SK".to_string()); + let de = &mut make_node_record(7777, true); + de.inner.country_code_opt = Some("DE".to_string()); + let at = &mut make_node_record(1325, true); + at.inner.country_code_opt = Some("AT".to_string()); + let pl = &mut make_node_record(2543, true); + pl.inner.country_code_opt = Some("PL".to_string()); + let db = &mut subject.neighborhood_database.clone(); + db.add_node(cz.clone()).unwrap(); + db.add_node(de.clone()).unwrap(); + db.add_node(us.clone()).unwrap(); + db.add_node(sk.clone()).unwrap(); + db.add_node(at.clone()).unwrap(); + db.add_node(pl.clone()).unwrap(); + let mut dual_edge = |a: &NodeRecord, b: &NodeRecord| { + db.add_arbitrary_full_neighbor(a.public_key(), b.public_key()); + }; + dual_edge(&subject.neighborhood_database.root(), cz); + dual_edge(cz, de); + dual_edge(cz, us); + dual_edge(us, sk); + dual_edge(us, at); + dual_edge(at, pl); + subject.neighborhood_database = db.clone(); + let subject_addr = subject.start(); + let peer_actors = peer_actors_builder().ui_gateway(ui_gateway).build(); + let cz_public_key = cz.inner.public_key.clone(); + let us_public_key = us.inner.public_key.clone(); + let sk_public_key = sk.inner.public_key.clone(); + let de_public_key = de.inner.public_key.clone(); + let at_public_key = at.inner.public_key.clone(); + let pl_public_key = pl.inner.public_key.clone(); + let assertion_msg = AssertionsMessage { + assertions: Box::new(move |neighborhood: &mut Neighborhood| { + assert_eq!( + neighborhood.user_exit_preferences.exit_countries, + vec!["SK".to_string(), "AT".to_string(), "PL".to_string(),] + ); + assert_eq!( + neighborhood.user_exit_preferences.location_preference, + ExitPreference::ExitCountryWithFallback + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&cz_public_key) + .unwrap() + .metadata + .country_undesirability, + UNREACHABLE_COUNTRY_PENALTY, + "cz We expecting {}, country is too close to be exit", + UNREACHABLE_COUNTRY_PENALTY + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&us_public_key) + .unwrap() + .metadata + .country_undesirability, + UNREACHABLE_COUNTRY_PENALTY, + "us We expecting {}, country is considered for exit location in fallback", + UNREACHABLE_COUNTRY_PENALTY + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&sk_public_key) + .unwrap() + .metadata + .country_undesirability, + 0u32, + "sk We expecting 0, country is with Priority: 1" + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&de_public_key) + .unwrap() + .metadata + .country_undesirability, + UNREACHABLE_COUNTRY_PENALTY, + "de We expecting {}, country is too close to be exit", + UNREACHABLE_COUNTRY_PENALTY + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&at_public_key) + .unwrap() + .metadata + .country_undesirability, + 1 * COUNTRY_UNDESIRABILITY_FACTOR, + "at We expecting {}, country is with Priority: 2", + 1 * COUNTRY_UNDESIRABILITY_FACTOR + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&pl_public_key) + .unwrap() + .metadata + .country_undesirability, + 2 * COUNTRY_UNDESIRABILITY_FACTOR, + "pl We expecting {}, country is with Priority: 3", + 2 * COUNTRY_UNDESIRABILITY_FACTOR + ); + }), + }; + subject_addr.try_send(BindMessage { peer_actors }).unwrap(); + + subject_addr.try_send(message).unwrap(); + subject_addr.try_send(assertion_msg).unwrap(); + + System::current().stop(); + system.run(); + + TestLogHandler::new().assert_logs_contain_in_order(vec![ + &format!( + "INFO: {}: Fallback Routing is set. Exit location set:", + test_name + ), + &"Country Codes: [\"CZ\", \"SK\"] - Priority: 1; Country Codes: [\"AT\", \"DE\"] - Priority: 2; Country Codes: [\"PL\"] - Priority: 3;" + ]); + } + + #[test] + fn no_exit_location_is_set_if_desired_country_codes_not_present_in_neighborhood() { + init_test_logging(); + let test_name = "exit_location_with_multiple_countries_and_priorities_can_be_changed_using_exit_location_msg"; + let request = UiSetExitLocationRequest { + fallback_routing: true, + exit_locations: vec![CountryCodes { + country_codes: vec!["CZ".to_string(), "SK".to_string(), "IN".to_string()], + priority: 1, + }], + show_countries: true, + }; + let message = NodeFromUiMessage { + client_id: 0, + body: request.tmb(0), + }; + let system = System::new(test_name); + let (ui_gateway, _recorder, arc_recorder) = make_recorder(); + let mut subject = make_standard_subject(); + subject.min_hops = Hops::TwoHops; + subject.logger = Logger::new(test_name); + let es = &mut make_node_record(3456, true); + es.inner.country_code_opt = Some("ES".to_string()); + let us = &mut make_node_record(4567, true); + us.inner.country_code_opt = Some("US".to_string()); + let hu = &mut make_node_record(5678, true); + hu.inner.country_code_opt = Some("US".to_string()); + let de = &mut make_node_record(7777, true); + de.inner.country_code_opt = Some("DE".to_string()); + let at = &mut make_node_record(1325, true); + at.inner.country_code_opt = Some("AT".to_string()); + let pl = &mut make_node_record(2543, true); + pl.inner.country_code_opt = Some("PL".to_string()); + let db = &mut subject.neighborhood_database.clone(); + db.add_node(es.clone()).unwrap(); + db.add_node(de.clone()).unwrap(); + db.add_node(us.clone()).unwrap(); + db.add_node(hu.clone()).unwrap(); + db.add_node(at.clone()).unwrap(); + db.add_node(pl.clone()).unwrap(); + let mut dual_edge = |a: &NodeRecord, b: &NodeRecord| { + db.add_arbitrary_full_neighbor(a.public_key(), b.public_key()); + }; + dual_edge(&subject.neighborhood_database.root(), es); + dual_edge(es, de); + dual_edge(es, us); + dual_edge(us, hu); + dual_edge(us, at); + dual_edge(at, pl); + subject.neighborhood_database = db.clone(); + let subject_addr = subject.start(); + let peer_actors = peer_actors_builder().ui_gateway(ui_gateway).build(); + let es_public_key = es.inner.public_key.clone(); + let us_public_key = us.inner.public_key.clone(); + let hu_public_key = hu.inner.public_key.clone(); + let de_public_key = de.inner.public_key.clone(); + let at_public_key = at.inner.public_key.clone(); + let pl_public_key = pl.inner.public_key.clone(); + let assertion_msg = AssertionsMessage { + assertions: Box::new(move |neighborhood: &mut Neighborhood| { + assert!(neighborhood.user_exit_preferences.exit_countries.is_empty(),); + assert_eq!( + neighborhood.user_exit_preferences.locations_opt, + Some(vec![ExitLocation { + country_codes: vec!["CZ".to_string(), "SK".to_string(), "IN".to_string()], + priority: 1 + }]) + ); + assert_eq!( + neighborhood.user_exit_preferences.db_countries, + vec![ + "AT".to_string(), + "DE".to_string(), + "PL".to_string(), + "US".to_string() + ] + ); + assert_eq!( + neighborhood.user_exit_preferences.location_preference, + ExitPreference::ExitCountryWithFallback + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&es_public_key) + .unwrap() + .metadata + .country_undesirability, + UNREACHABLE_COUNTRY_PENALTY, + "es We expecting {}, country is too close to be exit", + UNREACHABLE_COUNTRY_PENALTY + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&us_public_key) + .unwrap() + .metadata + .country_undesirability, + UNREACHABLE_COUNTRY_PENALTY, + "us We expecting {}, country is considered for exit location in fallback", + UNREACHABLE_COUNTRY_PENALTY + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&hu_public_key) + .unwrap() + .metadata + .country_undesirability, + UNREACHABLE_COUNTRY_PENALTY, + "hu We expecting {}, country is too close to be exit", + UNREACHABLE_COUNTRY_PENALTY + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&de_public_key) + .unwrap() + .metadata + .country_undesirability, + UNREACHABLE_COUNTRY_PENALTY, + "de We expecting {}, country is too close to be exit", + UNREACHABLE_COUNTRY_PENALTY + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&at_public_key) + .unwrap() + .metadata + .country_undesirability, + UNREACHABLE_COUNTRY_PENALTY, + "at We expecting {}, country is considered for exit location in fallback", + UNREACHABLE_COUNTRY_PENALTY + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&pl_public_key) + .unwrap() + .metadata + .country_undesirability, + UNREACHABLE_COUNTRY_PENALTY, + "pl We expecting {}, country is too close to be exit", + UNREACHABLE_COUNTRY_PENALTY + ); + }), + }; + subject_addr.try_send(BindMessage { peer_actors }).unwrap(); + + subject_addr.try_send(message).unwrap(); + subject_addr.try_send(assertion_msg).unwrap(); + + System::current().stop(); + system.run(); + + //println!("recorder: {:#?}", &recorder.try_into().unwrap()); + let exit_location_recording = &arc_recorder.lock().unwrap(); + let exit_handler_response = exit_location_recording + .get_record::(0) + .body + .payload + .clone(); + let log_handler = TestLogHandler::new(); + assert_eq!( + exit_handler_response, + Err(( + 9223372036854775816, + "Exit Countries: [\"AT\", \"DE\", \"PL\", \"US\"]\nExit Location: following desired countries are missing in Neighborhood [\"CZ\", \"SK\", \"IN\"]".to_string(), + )) + ); + log_handler.assert_logs_contain_in_order(vec![ + &format!( + "INFO: {}: Fallback Routing is set. Exit location set:", + test_name + ), + &"Country Codes: [\"CZ\", \"SK\", \"IN\"] - Priority: 1;", + &format!( + "WARN: {}: Exit Location: following desired countries are missing in Neighborhood [\"CZ\", \"SK\", \"IN\"]", + test_name + ), + ]); + } + + #[test] + fn exit_location_is_set_and_unset_with_fallback_routing_using_exit_location_msg() { + init_test_logging(); + let test_name = + "exit_location_is_set_and_unset_with_fallback_routing_using_exit_location_msg"; + let request = UiSetExitLocationRequest { + fallback_routing: false, + exit_locations: vec![ + CountryCodes { + country_codes: vec!["CZ".to_string()], + priority: 1, + }, + CountryCodes { + country_codes: vec!["FR".to_string()], + priority: 2, + }, + ], + show_countries: false, + }; + let set_exit_location_message = NodeFromUiMessage { + client_id: 8765, + body: request.tmb(1234), + }; + let mut subject = make_standard_subject(); + let system = System::new(test_name); + let (ui_gateway, _, ui_gateway_recording_arc) = make_recorder(); + subject.logger = Logger::new(test_name); + let cz = &mut make_node_record(3456, true); + cz.inner.country_code_opt = Some("CZ".to_string()); + let r = &make_node_record(4567, false); + let fr = &mut make_node_record(5678, false); + fr.inner.country_code_opt = Some("FR".to_string()); + let t = &make_node_record(7777, false); + let db = &mut subject.neighborhood_database.clone(); + db.add_node(cz.clone()).unwrap(); + db.add_node(t.clone()).unwrap(); + db.add_node(r.clone()).unwrap(); + db.add_node(fr.clone()).unwrap(); + let mut dual_edge = |a: &NodeRecord, b: &NodeRecord| { + db.add_arbitrary_full_neighbor(a.public_key(), b.public_key()); + }; + dual_edge(&subject.neighborhood_database.root(), cz); + dual_edge(cz, t); + dual_edge(cz, r); + dual_edge(r, fr); + subject.neighborhood_database = db.clone(); + let subject_addr = subject.start(); + let peer_actors = peer_actors_builder().ui_gateway(ui_gateway).build(); + let cz_public_key = cz.inner.public_key.clone(); + let r_public_key = r.inner.public_key.clone(); + let fr_public_key = fr.inner.public_key.clone(); + let t_public_key = t.inner.public_key.clone(); + let assert_country_undesirability_populated = AssertionsMessage { + assertions: Box::new(move |neighborhood: &mut Neighborhood| { + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&cz_public_key) + .unwrap() + .metadata + .country_undesirability, + 0u32, + "CZ - We expecting zero, country is with Priority: 1" + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&r_public_key) + .unwrap() + .metadata + .country_undesirability, + 0u32, + "We expecting 0, country is not considered for exit location, so country_undesirability doesn't matter" + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&fr_public_key) + .unwrap() + .metadata + .country_undesirability, + 1 * COUNTRY_UNDESIRABILITY_FACTOR, + "FR - We expecting {}, country is with Priority: 2", + 1 * COUNTRY_UNDESIRABILITY_FACTOR + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&t_public_key) + .unwrap() + .metadata + .country_undesirability, + 0u32, + "We expecting 0, country is not considered for exit location, so country_undesirability doesn't matter" + ); + }), + }; + let assert_neighborhood_exit_location = AssertionsMessage { + assertions: Box::new(move |neighborhood: &mut Neighborhood| { + assert_eq!( + neighborhood.user_exit_preferences.exit_countries, + vec!["FR".to_string()] + ); + assert_eq!( + neighborhood.user_exit_preferences.location_preference, + ExitPreference::ExitCountryNoFallback + ); + }), + }; + let request_2 = UiSetExitLocationRequest { + fallback_routing: true, + exit_locations: vec![], + show_countries: false, + }; + let clear_exit_location_message = NodeFromUiMessage { + client_id: 6543, + body: request_2.tmb(7894), + }; + let cz_public_key_2 = cz.inner.public_key.clone(); + let r_public_key_2 = r.inner.public_key.clone(); + let fr_public_key_2 = fr.inner.public_key.clone(); + let t_public_key_2 = t.inner.public_key.clone(); + let assert_country_undesirability_and_exit_preference_cleared = AssertionsMessage { + assertions: Box::new(move |neighborhood: &mut Neighborhood| { + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&cz_public_key_2) + .unwrap() + .metadata + .country_undesirability, + 0u32, + "We expecting zero, exit_location was unset" + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&r_public_key_2) + .unwrap() + .metadata + .country_undesirability, + 0u32, + "We expecting zero, exit_location was unset" + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&fr_public_key_2) + .unwrap() + .metadata + .country_undesirability, + 0u32, + "We expecting zero, exit_location was unset" + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&t_public_key_2) + .unwrap() + .metadata + .country_undesirability, + 0u32, + "We expecting zero, exit_location was unset" + ); + assert_eq!( + neighborhood.user_exit_preferences.exit_countries.is_empty(), + true + ); + assert_eq!( + neighborhood.user_exit_preferences.location_preference, + ExitPreference::Nothing + ) + }), + }; + + subject_addr.try_send(BindMessage { peer_actors }).unwrap(); + subject_addr.try_send(set_exit_location_message).unwrap(); + subject_addr + .try_send(assert_country_undesirability_populated) + .unwrap(); + subject_addr + .try_send(assert_neighborhood_exit_location) + .unwrap(); + subject_addr.try_send(clear_exit_location_message).unwrap(); + subject_addr + .try_send(assert_country_undesirability_and_exit_preference_cleared) + .unwrap(); + + System::current().stop(); + system.run(); + let ui_gateway_recording = ui_gateway_recording_arc.lock().unwrap(); + let record_one: &NodeToUiMessage = ui_gateway_recording.get_record(0); + let record_two: &NodeToUiMessage = ui_gateway_recording.get_record(1); + + assert_eq!(ui_gateway_recording.len(), 2); + assert_eq!( + record_one, + &NodeToUiMessage { + target: ClientId(8765), + body: MessageBody { + opcode: "exit_location".to_string(), + path: Conversation(1234), + payload: Err( + (9223372036854775816, "Exit Location: following desired countries are missing in Neighborhood [\"CZ\"]".to_string()) + ) + } + } + ); + assert_eq!( + record_two, + &NodeToUiMessage { + target: MessageTarget::ClientId(6543), + body: UiSetExitLocationResponse {}.tmb(7894), + } + ); + TestLogHandler::new().assert_logs_contain_in_order(vec![ + &format!( + "INFO: {}: Fallback Routing NOT set. Exit location set: Country Codes: [\"CZ\"] - Priority: 1; Country Codes: [\"FR\"] - Priority: 2;", + test_name + ), + &format!( + "WARN: {}: Exit Location: following desired countries are missing in Neighborhood [\"CZ\"]", + test_name + ), + &format!( + "INFO: {}: Fallback Routing is set. Exit location unset.", + test_name + ), + ]); + } + #[test] fn min_hops_can_be_changed_during_runtime_using_configuration_change_msg() { init_test_logging(); @@ -3221,6 +4396,124 @@ mod tests { ); } + /* + Database: + + A---B---C---D---E + | | | | | + F---G---H---I---J + | | | | | + K---L---M---N---O + | | | | | + P---Q---R---S---T + | | | | | + U---V---W---X---Y + + All these Nodes are standard-mode. L is the root Node. + */ + #[test] + fn find_exit_location_test() { + let mut subject = make_standard_subject(); + let db = &mut subject.neighborhood_database; + let mut generator = 1000; + let mut make_node = |db: &mut NeighborhoodDatabase| { + let node = &db.add_node(make_node_record(generator, true)).unwrap(); + generator += 1; + node.clone() + }; + let mut make_row = |db: &mut NeighborhoodDatabase| { + let n1 = make_node(db); + let n2 = make_node(db); + let n3 = make_node(db); + let n4 = make_node(db); + let n5 = make_node(db); + db.add_arbitrary_full_neighbor(&n1, &n2); + db.add_arbitrary_full_neighbor(&n2, &n3); + db.add_arbitrary_full_neighbor(&n3, &n4); + db.add_arbitrary_full_neighbor(&n4, &n5); + (n1, n2, n3, n4, n5) + }; + let join_rows = |db: &mut NeighborhoodDatabase, first_row, second_row| { + let (f1, f2, f3, f4, f5) = first_row; + let (s1, s2, s3, s4, s5) = second_row; + db.add_arbitrary_full_neighbor(f1, s1); + db.add_arbitrary_full_neighbor(f2, s2); + db.add_arbitrary_full_neighbor(f3, s3); + db.add_arbitrary_full_neighbor(f4, s4); + db.add_arbitrary_full_neighbor(f5, s5); + }; + let designate_root_node = |db: &mut NeighborhoodDatabase, key: &PublicKey| { + let root_node_key = db.root_key().clone(); + db.set_root_key(key); + db.remove_node(&root_node_key); + }; + let (a, b, c, d, e) = make_row(db); + let (f, g, h, i, j) = make_row(db); + let (k, l, m, n, o) = make_row(db); + let (p, q, r, s, t) = make_row(db); + let (u, v, w, x, y) = make_row(db); + join_rows(db, (&a, &b, &c, &d, &e), (&f, &g, &h, &i, &j)); + join_rows(db, (&f, &g, &h, &i, &j), (&k, &l, &m, &n, &o)); + join_rows(db, (&k, &l, &m, &n, &o), (&p, &q, &r, &s, &t)); + join_rows(db, (&p, &q, &r, &s, &t), (&u, &v, &w, &x, &y)); + designate_root_node(db, &l); + + let (_routes, mut exit_nodes) = subject.find_exit_location(&l, 3, 10_000); + + let total_exit_nodes = exit_nodes.len(); + exit_nodes.sort(); + exit_nodes.dedup(); + let dedup_len = exit_nodes.len(); + assert_eq!(total_exit_nodes, dedup_len); + assert_eq!(total_exit_nodes, 20); + } + + #[test] + fn find_exit_locations_in_row_structure_test() { + let mut subject = make_standard_subject(); + let db = &mut subject.neighborhood_database; + let mut generator = 1000; + let mut make_node = |db: &mut NeighborhoodDatabase| { + let node = &db.add_node(make_node_record(generator, true)).unwrap(); + generator += 1; + node.clone() + }; + let n1 = make_node(db); + let n2 = make_node(db); + let n3 = make_node(db); + let n4 = make_node(db); + let n5 = make_node(db); + let f1 = make_node(db); + let f2 = make_node(db); + let f3 = make_node(db); + let f4 = make_node(db); + let f5 = make_node(db); + db.add_arbitrary_full_neighbor(&n1, &n2); + db.add_arbitrary_full_neighbor(&n2, &n3); + db.add_arbitrary_full_neighbor(&n3, &n4); + db.add_arbitrary_full_neighbor(&n4, &n5); + db.add_arbitrary_full_neighbor(&n5, &f1); + db.add_arbitrary_full_neighbor(&f1, &f2); + db.add_arbitrary_full_neighbor(&f2, &f3); + db.add_arbitrary_full_neighbor(&f3, &f4); + db.add_arbitrary_full_neighbor(&f4, &f5); + let designate_root_node = |db: &mut NeighborhoodDatabase, key: &PublicKey| { + let root_node_key = db.root_key().clone(); + db.set_root_key(key); + db.remove_node(&root_node_key); + }; + designate_root_node(db, &n1); + + let (_routes, mut exit_nodes) = subject.find_exit_location(&n1, 3, 10_000); + + let total_exit_nodes = exit_nodes.len(); + exit_nodes.sort(); + exit_nodes.dedup(); + let dedup_len = exit_nodes.len(); + assert_eq!(total_exit_nodes, dedup_len); + assert_eq!(total_exit_nodes, 7); + } + /* Database: @@ -3254,14 +4547,16 @@ mod tests { let route_opt = subject.find_best_route_segment(p, None, 2, 10000, RouteDirection::Over, None); - assert_eq!(route_opt.unwrap(), vec![p, s, t]); + let route_ref_opt = remap_keys_to_reference(route_opt.as_ref()); + assert_eq!(route_ref_opt.unwrap(), vec![p, s, t]); // no [p, r, s] or [p, s, r] because s and r are both neighbors of p and can't exit for it // At least two hops over from p to t let route_opt = subject.find_best_route_segment(p, Some(t), 2, 10000, RouteDirection::Over, None); - assert_eq!(route_opt.unwrap(), vec![p, s, t]); + let route_ref_opt = remap_keys_to_reference(route_opt.as_ref()); + assert_eq!(route_ref_opt.unwrap(), vec![p, s, t]); // At least two hops over from t to p let route_opt = @@ -3274,7 +4569,8 @@ mod tests { let route_opt = subject.find_best_route_segment(t, Some(p), 2, 10000, RouteDirection::Back, None); - assert_eq!(route_opt.unwrap(), vec![t, s, p]); + let route_ref_opt = remap_keys_to_reference(route_opt.as_ref()); + assert_eq!(route_ref_opt.unwrap(), vec![t, s, p]); // p is consume-only, but it's the originating Node, so including it is okay // At least two hops from p to Q - impossible @@ -3284,6 +4580,12 @@ mod tests { assert_eq!(route_opt, None); } + fn remap_keys_to_reference(route_opt: Option<&Vec>) -> Option> { + route_opt.map(|result| { + result.iter().collect() + }) + } + /* Database: @@ -3304,6 +4606,13 @@ mod tests { fn route_optimization_test() { let mut subject = make_standard_subject(); let db = &mut subject.neighborhood_database; + let (recipient, _) = make_node_to_ui_recipient(); + subject.node_to_ui_recipient_opt = Some(recipient); + let message = UiSetExitLocationRequest { + fallback_routing: true, + exit_locations: vec![], + show_countries: false, + }; let mut generator = 1000; let mut make_node = |db: &mut NeighborhoodDatabase| { let node = &db.add_node(make_node_record(generator, true)).unwrap(); @@ -3331,11 +4640,9 @@ mod tests { db.add_arbitrary_full_neighbor(f4, s4); db.add_arbitrary_full_neighbor(f5, s5); }; - let designate_root_node = |db: &mut NeighborhoodDatabase, key| { - let root_node_key = db.root().public_key().clone(); - let node = db.node_by_key(key).unwrap().clone(); - db.root_mut().inner = node.inner.clone(); - db.root_mut().metadata = node.metadata.clone(); + let designate_root_node = |db: &mut NeighborhoodDatabase, key: &PublicKey| { + let root_node_key = db.root_key().clone(); + db.set_root_key(key); db.remove_node(&root_node_key); }; let (a, b, c, d, e) = make_row(db); @@ -3348,15 +4655,126 @@ mod tests { join_rows(db, (&k, &l, &m, &n, &o), (&p, &q, &r, &s, &t)); join_rows(db, (&p, &q, &r, &s, &t), (&u, &v, &w, &x, &y)); designate_root_node(db, &l); + subject.handle_exit_location_message(message, 0, 0); let before = Instant::now(); // All the target-designated routes from L to N let route = subject - .find_best_route_segment(&l, Some(&n), 3, 10000, RouteDirection::Back, None) - .unwrap(); + .find_best_route_segment(&l, Some(&n), 3, 10000, RouteDirection::Back, None); let after = Instant::now(); - assert_eq!(route, vec![&l, &g, &h, &i, &n]); // Cheaper than [&l, &q, &r, &s, &n] + let route_ref_opt = remap_keys_to_reference(route.as_ref()); + assert_eq!(route_ref_opt.unwrap(), vec![&l, &g, &h, &i, &n]); // Cheaper than [&l, &q, &r, &s, &n] + let interval = after.duration_since(before); + assert!( + interval.as_millis() <= 100, + "Should have calculated route in <=100ms, but was {}ms", + interval.as_millis() + ); + } + + /* Complex testing of country_undesirability on large network with aim to find fallback routing and non fallback routing mechanisms + + Database: + + A---B---C---D---E + | | | | | + F---G---H---I---J + | | | | | + K---L---M---N---O + | | | | | + P---Q---R---S---T + | | | | | + U---V---W---X---Y + + All these Nodes are standard-mode. L is the root Node. + + */ + // TODO drive in the test where C route will be more expensive, than T route, so we have to pick up T exit-node + #[test] + fn route_optimization_country_codes() { + let mut subject = make_standard_subject(); + let db = &mut subject.neighborhood_database; + let (recipient, _) = make_node_to_ui_recipient(); + subject.node_to_ui_recipient_opt = Some(recipient); + let message = UiSetExitLocationRequest { + fallback_routing: false, + exit_locations: vec![CountryCodes { + country_codes: vec!["CZ".to_string()], + priority: 1, + }], + show_countries: false, + }; + println!("db {:?}", db.root()); + let mut generator = 1000; + let mut make_node = |db: &mut NeighborhoodDatabase| { + let node = &db.add_node(make_node_record(generator, true)).unwrap(); + generator += 1; + node.clone() + }; + let mut make_row = |db: &mut NeighborhoodDatabase| { + let n1 = make_node(db); + let n2 = make_node(db); + let n3 = make_node(db); + let n4 = make_node(db); + let n5 = make_node(db); + db.add_arbitrary_full_neighbor(&n1, &n2); + db.add_arbitrary_full_neighbor(&n2, &n3); + db.add_arbitrary_full_neighbor(&n3, &n4); + db.add_arbitrary_full_neighbor(&n4, &n5); + (n1, n2, n3, n4, n5) + }; + let join_rows = |db: &mut NeighborhoodDatabase, first_row, second_row| { + let (f1, f2, f3, f4, f5) = first_row; + let (s1, s2, s3, s4, s5) = second_row; + db.add_arbitrary_full_neighbor(f1, s1); + db.add_arbitrary_full_neighbor(f2, s2); + db.add_arbitrary_full_neighbor(f3, s3); + db.add_arbitrary_full_neighbor(f4, s4); + db.add_arbitrary_full_neighbor(f5, s5); + }; + let designate_root_node = |db: &mut NeighborhoodDatabase, key: &PublicKey| { + let root_node_key = db.root_key().clone(); + db.set_root_key(key); + db.remove_node(&root_node_key); + }; + let (a, b, c, d, e) = make_row(db); + let (f, g, h, i, j) = make_row(db); + let (k, l, m, n, o) = make_row(db); + let (p, q, r, s, t) = make_row(db); + let (u, v, w, x, y) = make_row(db); + + join_rows(db, (&a, &b, &c, &d, &e), (&f, &g, &h, &i, &j)); + join_rows(db, (&f, &g, &h, &i, &j), (&k, &l, &m, &n, &o)); + join_rows(db, (&k, &l, &m, &n, &o), (&p, &q, &r, &s, &t)); + join_rows(db, (&p, &q, &r, &s, &t), (&u, &v, &w, &x, &y)); + + let mut checkdb = db.clone(); + designate_root_node(db, &l); + + db.node_by_key_mut(&c).unwrap().inner.country_code_opt = Some("CZ".to_string()); + checkdb.node_by_key_mut(&c).unwrap().inner.country_code_opt = Some("CZ".to_string()); + db.node_by_key_mut(&t).unwrap().inner.country_code_opt = Some("CZ".to_string()); + checkdb.node_by_key_mut(&t).unwrap().inner.country_code_opt = Some("CZ".to_string()); + + subject.handle_exit_location_message(message, 0, 0); + let before = Instant::now(); + + let route_cz = + subject.find_best_route_segment(&l, None, 2, 10000, RouteDirection::Over, None); + + let after = Instant::now(); + let exit_node = checkdb.node_by_key( + &route_cz + .as_ref() + .unwrap() + .get(route_cz.as_ref().unwrap().len() - 1) + .unwrap(), + ); + assert_eq!( + exit_node.unwrap().inner.country_code_opt, + Some("CZ".to_string()) + ); let interval = after.duration_since(before); assert!( interval.as_millis() <= 100, @@ -3373,6 +4791,160 @@ mod tests { Test is written from the standpoint of P. Node q is non-routing. */ + #[test] + fn find_best_segment_traces_unreachable_country_code_exit_node() { + init_test_logging(); + let mut subject = make_standard_subject(); + let (recipient, _) = make_node_to_ui_recipient(); + subject.node_to_ui_recipient_opt = Some(recipient); + subject.user_exit_preferences.location_preference = ExitPreference::ExitCountryWithFallback; + let message = UiSetExitLocationRequest { + fallback_routing: false, + exit_locations: vec![CountryCodes { + country_codes: vec!["CZ".to_string()], + priority: 1, + }], + show_countries: false, + }; + let db = &mut subject.neighborhood_database; + let p = &db.root_mut().public_key().clone(); + let a = &db.add_node(make_node_record(2345, true)).unwrap(); + let b = &db.add_node(make_node_record(5678, true)).unwrap(); + let c = &db.add_node(make_node_record(1234, true)).unwrap(); + db.add_arbitrary_full_neighbor(p, c); + db.add_arbitrary_full_neighbor(c, b); + db.add_arbitrary_full_neighbor(c, a); + subject.handle_exit_location_message(message, 0, 0); + + let route_cz = + subject.find_best_route_segment(p, None, 2, 10000, RouteDirection::Over, None); + + assert_eq!(route_cz, None); + } + + #[test] + fn route_for_au_country_code_is_constructed_with_fallback_routing() { + let mut subject = make_standard_subject(); + //let db = &mut subject.neighborhood_database; + let p = &subject + .neighborhood_database + .root_mut() + .public_key() + .clone(); + let a = &subject + .neighborhood_database + .add_node(make_node_record(2345, true)) + .unwrap(); + let b = &subject + .neighborhood_database + .add_node(make_node_record(5678, true)) + .unwrap(); + let c = &subject + .neighborhood_database + .add_node(make_node_record(1234, true)) + .unwrap(); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(p, b); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(b, c); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(b, a); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(a, c); + let cdb = subject.neighborhood_database.clone(); + let (recipient, _) = make_node_to_ui_recipient(); + subject.node_to_ui_recipient_opt = Some(recipient); + let message = UiSetExitLocationRequest { + fallback_routing: true, + exit_locations: vec![CountryCodes { + country_codes: vec!["AU".to_string()], + priority: 1, + }], + show_countries: false, + }; + subject.handle_exit_location_message(message, 0, 0); + + let route_au = + subject.find_best_route_segment(p, None, 2, 10000, RouteDirection::Over, None); + + let exit_node = cdb.node_by_key( + &route_au + .as_ref() + .unwrap() + .get(route_au.as_ref().unwrap().len() - 1) + .unwrap(), + ); + assert_eq!( + exit_node.unwrap().inner.country_code_opt, + Some("AU".to_string()) + ); + } + + #[test] + fn route_for_fr_country_code_is_constructed_without_fallback_routing() { + let mut subject = make_standard_subject(); + let p = &subject + .neighborhood_database + .root_mut() + .public_key() + .clone(); + let a = &subject + .neighborhood_database + .add_node(make_node_record(2345, true)) + .unwrap(); + let b = &subject + .neighborhood_database + .add_node(make_node_record(5678, true)) + .unwrap(); + let c = &subject + .neighborhood_database + .add_node(make_node_record(1234, true)) + .unwrap(); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(p, b); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(b, c); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(b, a); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(a, c); + let cdb = subject.neighborhood_database.clone(); + let (recipient, _) = make_node_to_ui_recipient(); + subject.node_to_ui_recipient_opt = Some(recipient); + let message = UiSetExitLocationRequest { + fallback_routing: false, + exit_locations: vec![CountryCodes { + country_codes: vec!["FR".to_string()], + priority: 1, + }], + show_countries: false, + }; + subject.handle_exit_location_message(message, 0, 0); + + let route_fr = + subject.find_best_route_segment(p, None, 2, 10000, RouteDirection::Over, None); + + let exit_node = cdb.node_by_key( + &route_fr + .as_ref() + .unwrap() + .get(route_fr.as_ref().unwrap().len() - 1) + .unwrap(), + ); + assert_eq!( + exit_node.unwrap().inner.country_code_opt, + Some("FR".to_string()) + ); + } + #[test] fn cant_route_through_non_routing_node() { let mut subject = make_standard_subject(); @@ -3575,7 +5147,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, earning_wallet.clone(), consuming_wallet.clone(), @@ -3976,7 +5547,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; let bootstrap_config = bc_from_nc_plus(neighborhood_config, make_wallet("earning"), None, "test"); @@ -4373,6 +5943,59 @@ mod tests { .exists_log_containing("INFO: Neighborhood: Changed public IP from 1.2.3.4 to 4.3.2.1"); } + #[test] + fn handle_gossip_produces_new_entry_in_db_countries() { + init_test_logging(); + let subject_node = make_global_cryptde_node_record(5555, true); // 9e7p7un06eHs6frl5A + let first_neighbor = make_node_record(1050, true); + let mut subject = neighborhood_from_nodes(&subject_node, Some(&first_neighbor)); + let second_neighbor = make_node_record(1234, false); + let new_neighbor = make_node_record(2345, false); + let first_key = subject + .neighborhood_database + .add_node(first_neighbor) + .unwrap(); + let second_key = subject + .neighborhood_database + .add_node(second_neighbor) + .unwrap(); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(subject_node.public_key(), &first_key); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(&first_key, &second_key); + subject.user_exit_preferences.db_countries = subject.init_db_countries(); + let assertion_db_countries = subject.user_exit_preferences.db_countries.clone(); + let peer_actors = peer_actors_builder().build(); + subject.handle_bind_message(BindMessage { peer_actors }); + + let mut neighbor_db = subject.neighborhood_database.clone(); + neighbor_db.add_node(new_neighbor.clone()).unwrap(); + neighbor_db.this_node = first_key.clone(); + neighbor_db.add_arbitrary_full_neighbor(&second_key, new_neighbor.public_key()); + let mut new_second_neighbor = neighbor_db.node_by_key_mut(&second_key).unwrap(); + new_second_neighbor.inner.version = 2; + new_second_neighbor.resign(); + let gossip = GossipBuilder::new(&neighbor_db) + .node(&first_key, true) + .node(&second_key, false) + .node(new_neighbor.public_key(), false) + .build(); + + subject.handle_gossip( + gossip, + SocketAddr::from_str("1.0.5.0:1050").unwrap(), + make_cpm_recipient().0, + ); + + assert!(assertion_db_countries.is_empty()); + assert_eq!( + &subject.user_exit_preferences.db_countries, + &["FR".to_string()] + ) + } + #[test] fn neighborhood_sends_from_gossip_producer_when_acceptance_introductions_are_not_provided() { init_test_logging(); @@ -4722,7 +6345,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, this_node_inside.earning_wallet(), None, @@ -4786,7 +6408,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, NodeRecord::earning_wallet_from_key(&cryptde.public_key()), NodeRecord::consuming_wallet_from_key(&cryptde.public_key()), @@ -4844,7 +6465,6 @@ mod tests { rate_pack(100), ), min_hops: min_hops_in_neighborhood, - country: "ZZ".to_string(), }, make_wallet("earning"), None, @@ -4888,7 +6508,6 @@ mod tests { rate_pack(100), ), min_hops: min_hops_in_neighborhood, - country: "ZZ".to_string(), }, make_wallet("earning"), None, @@ -4982,6 +6601,7 @@ mod tests { return_component_opt: None, payload_size: 10000, hostname_opt: None, + target_country_opt: None, }; let unsuccessful_three_hop_route = addr.send(three_hop_route_request); let asserted_node_record = a.clone(); @@ -5079,7 +6699,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string() }, earning_wallet.clone(), consuming_wallet.clone(), @@ -5142,7 +6761,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string() }, earning_wallet.clone(), consuming_wallet.clone(), @@ -5210,7 +6828,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string() }, earning_wallet.clone(), consuming_wallet.clone(), @@ -5275,7 +6892,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string() }, node_record.earning_wallet(), None, @@ -5352,6 +6968,7 @@ mod tests { return_component_opt: Some(Component::ProxyServer), payload_size: 10000, hostname_opt: None, + target_country_opt: None, }); assert_eq!( @@ -5397,6 +7014,7 @@ mod tests { return_component_opt: Some(Component::ProxyServer), payload_size: 10000, hostname_opt: None, + target_country_opt: None, }); let next_door_neighbor_cryptde = @@ -5451,6 +7069,7 @@ mod tests { return_component_opt: Some(Component::ProxyServer), payload_size: 10000, hostname_opt: None, + target_country_opt: None, }); let assert_hops = |cryptdes: Vec, route: &[CryptData]| { @@ -5552,6 +7171,7 @@ mod tests { return_component_opt: Some(Component::ProxyServer), payload_size, hostname_opt: None, + target_country_opt: None, }) .unwrap(); @@ -5788,7 +7408,6 @@ mod tests { NeighborhoodConfig { mode: NeighborhoodMode::ZeroHop, min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, make_wallet("earning"), None, @@ -6159,7 +7778,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; let bootstrap_config = bc_from_nc_plus(neighborhood_config, make_wallet("earning"), None, test_name); @@ -6184,7 +7802,6 @@ mod tests { NeighborhoodConfig { mode: NeighborhoodMode::ConsumeOnly(vec![make_node_descriptor(make_ip(1))]), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, make_wallet("earning"), None, diff --git a/node/src/neighborhood/neighborhood_database.rs b/node/src/neighborhood/neighborhood_database.rs index acd6ee99c..176e41513 100644 --- a/node/src/neighborhood/neighborhood_database.rs +++ b/node/src/neighborhood/neighborhood_database.rs @@ -25,7 +25,7 @@ pub const ISOLATED_NODE_GRACE_PERIOD_SECS: u32 = 30; #[derive(Clone)] pub struct NeighborhoodDatabase { - this_node: PublicKey, + pub this_node: PublicKey, by_public_key: HashMap, by_ip_addr: HashMap, logger: Logger, @@ -77,6 +77,10 @@ impl NeighborhoodDatabase { self.node_by_key(&self.this_node).expect("Internal error") } + pub fn root_key(&self) -> &PublicKey { + &self.this_node + } + pub fn root_mut(&mut self) -> &mut NodeRecord { let root_key = &self.this_node.clone(); self.node_by_key_mut(root_key).expect("Internal error") @@ -94,6 +98,13 @@ impl NeighborhoodDatabase { self.by_public_key.get_mut(public_key) } + pub fn nodes_mut(&mut self) -> Vec<&mut NodeRecord> { + self.by_public_key + .iter_mut() + .map(|(_key, node_record)| node_record) + .collect() + } + pub fn node_by_ip(&self, ip_addr: &IpAddr) -> Option<&NodeRecord> { match self.by_ip_addr.get(ip_addr) { Some(key) => self.node_by_key(key), @@ -288,11 +299,16 @@ impl NeighborhoodDatabase { to: k.clone(), }) }); + let country_code = match &nr.inner.country_code_opt { + Some(cc) => cc.clone(), + None => "ZZ".to_string(), + }; node_renderables.push(NodeRenderable { inner: Some(NodeRenderableInner { version: nr.version(), accepts_connections: nr.accepts_connections(), routes_data: nr.routes_data(), + country_code }), public_key: public_key.clone(), node_addr: nr.node_addr_opt(), @@ -370,7 +386,9 @@ mod tests { use crate::sub_lib::cryptde_null::CryptDENull; use crate::sub_lib::utils::time_t_timestamp; use crate::test_utils::assert_string_contains; - use crate::test_utils::neighborhood_test_utils::{db_from_node, make_node_record}; + use crate::test_utils::neighborhood_test_utils::{ + db_from_node, make_node_record, make_segmented_ip, make_segments, + }; use masq_lib::constants::DEFAULT_CHAIN; use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN; use std::iter::FromIterator; @@ -561,6 +579,50 @@ mod tests { ); } + #[test] + fn nodes_mut_works() { + let root_node = make_node_record(1234, true); + let node_a = make_node_record(2345, false); + let node_b = make_node_record(3456, true); + let mut subject = NeighborhoodDatabase::new( + root_node.public_key(), + (&root_node).into(), + Wallet::from_str("0x0000000000000000000000000000000000004444").unwrap(), + &CryptDENull::from(root_node.public_key(), TEST_DEFAULT_CHAIN), + ); + subject.add_node(node_a.clone()).unwrap(); + subject.add_node(node_b.clone()).unwrap(); + let mut iterator: u16 = 7890; + let mut keys_nums: Vec<(PublicKey, u16)> = vec![]; + + let mutable_nodes = subject.nodes_mut(); + for node in mutable_nodes { + let (seg1, seg2, seg3, seg4) = make_segments(iterator); + node.metadata.node_addr_opt = Some(NodeAddr::new( + &make_segmented_ip(seg1, seg2, seg3, seg4), + &[iterator], + )); + keys_nums.push((node.inner.public_key.clone(), iterator)); + iterator += 1; + } + + for (pub_key, num) in keys_nums { + let (seg1, seg2, seg3, seg4) = make_segments(num); + assert_eq!( + &subject + .node_by_key(&pub_key) + .unwrap() + .clone() + .metadata + .node_addr_opt, + &Some(NodeAddr::new( + &make_segmented_ip(seg1, seg2, seg3, seg4), + &[num] + )) + ); + } + } + #[test] fn add_half_neighbor_works() { let this_node = make_node_record(1234, true); diff --git a/node/src/neighborhood/node_record.rs b/node/src/neighborhood/node_record.rs index 2a944f138..a84349e8f 100644 --- a/node/src/neighborhood/node_record.rs +++ b/node/src/neighborhood/node_record.rs @@ -347,7 +347,7 @@ pub struct NodeRecordMetadata { pub node_addr_opt: Option, pub unreachable_hosts: HashSet, pub node_location_opt: Option, - pub node_distrust_score: u32, + pub country_undesirability: u32, //TODO introduce various scores for latency, reliability and so } @@ -358,7 +358,7 @@ impl NodeRecordMetadata { node_addr_opt: None, unreachable_hosts: Default::default(), node_location_opt: None, - node_distrust_score: Default::default(), + country_undesirability: 0u32, } } } diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index a45a2969a..135d3b20f 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -305,7 +305,7 @@ pub trait DirsWrapper: Send { fn dup(&self) -> Box; // because implementing Clone for traits is problematic. } -pub struct DirsWrapperReal; +pub struct DirsWrapperReal {} impl DirsWrapper for DirsWrapperReal { fn data_dir(&self) -> Option { @@ -315,7 +315,19 @@ impl DirsWrapper for DirsWrapperReal { home_dir() } fn dup(&self) -> Box { - Box::new(DirsWrapperReal) + Box::new(DirsWrapperReal::default()) + } +} + +impl DirsWrapperReal { + pub fn new() -> Self { + Self {} + } +} + +impl Default for DirsWrapperReal { + fn default() -> Self { + DirsWrapperReal::new() } } @@ -348,7 +360,7 @@ mod tests { "/nonexistent_home/nonexistent_alice".to_string(), )), ); - let chain_name = "polygon-mumbai"; + let chain_name = "polygon-amoy"; let result = data_directory_from_context(&dirs_wrapper, &real_user, Chain::from(chain_name)); @@ -356,7 +368,7 @@ mod tests { assert_eq!( result, PathBuf::from( - "/nonexistent_home/nonexistent_alice/.local/share/MASQ/polygon-mumbai".to_string() + "/nonexistent_home/nonexistent_alice/.local/share/MASQ/polygon-amoy".to_string() ) ) } @@ -377,7 +389,8 @@ 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(); + determine_user_specific_data(&DirsWrapperReal::default(), &app, args_vec.as_slice()) + .unwrap(); assert_eq!( &format!( @@ -418,7 +431,8 @@ 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(); + determine_user_specific_data(&DirsWrapperReal::default(), &app, args_vec.as_slice()) + .unwrap(); assert_eq!( format!( "{}", @@ -454,7 +468,8 @@ mod tests { let app = determine_config_file_path_app(); let user_specific_data = - determine_user_specific_data(&DirsWrapperReal {}, &app, args_vec.as_slice()).unwrap(); + determine_user_specific_data(&DirsWrapperReal::default(), &app, args_vec.as_slice()) + .unwrap(); assert_eq!( "/tmp/booga.toml", @@ -474,7 +489,7 @@ mod tests { let args_vec: Vec = args.into(); let user_specific_data = determine_user_specific_data( - &DirsWrapperReal {}, + &DirsWrapperReal::default(), &determine_config_file_path_app(), args_vec.as_slice(), ) @@ -498,7 +513,7 @@ mod tests { let args_vec: Vec = args.into(); let user_specific_data = determine_user_specific_data( - &DirsWrapperReal {}, + &DirsWrapperReal::default(), &determine_config_file_path_app(), args_vec.as_slice(), ) @@ -521,7 +536,7 @@ mod tests { let args_vec: Vec = args.into(); let user_specific_data = determine_user_specific_data( - &DirsWrapperReal {}, + &DirsWrapperReal::default(), &determine_config_file_path_app(), args_vec.as_slice(), ) @@ -545,7 +560,7 @@ mod tests { let args_vec: Vec = args.into(); let user_specific_data = determine_user_specific_data( - &DirsWrapperReal {}, + &DirsWrapperReal::default(), &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 49cb35703..97f0da35e 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -63,7 +63,7 @@ impl Default for NodeConfiguratorStandardPrivileged { impl NodeConfiguratorStandardPrivileged { pub fn new() -> Self { Self { - dirs_wrapper: Box::new(DirsWrapperReal {}), + dirs_wrapper: Box::new(DirsWrapperReal::default()), } } } @@ -375,8 +375,10 @@ mod tests { use crate::test_utils::unshared_test_utils::{ 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 crate::test_utils::{ + assert_string_contains, main_cryptde, + make_node_base_dir_and_return_its_absolute_and_relative_path_to_os_home_dir, ArgsBuilder, + }; use masq_lib::blockchains::chains::Chain; use masq_lib::constants::DEFAULT_CHAIN; use masq_lib::multi_config::VirtualCommandLine; @@ -597,8 +599,12 @@ mod tests { ) .unwrap(); - privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut bootstrapper_config) - .unwrap(); + privileged_parse_args( + &DirsWrapperReal::default(), + &multi_config, + &mut bootstrapper_config, + ) + .unwrap(); let node_parse_args_configurator = UnprivilegedParseArgsConfigurationDaoNull {}; node_parse_args_configurator .unprivileged_parse_args( @@ -660,13 +666,13 @@ mod tests { "ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01", ) .param("--real-user", "999:999:/home/booga") - .param("--chain", "polygon-mumbai"); + .param("--chain", "polygon-amoy"); let mut config = BootstrapperConfig::new(); let vcls: Vec> = vec![Box::new(CommandLineVcl::new(args.into()))]; let multi_config = make_new_multi_config(&app_node(), vcls).unwrap(); - privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut config).unwrap(); + privileged_parse_args(&DirsWrapperReal::default(), &multi_config, &mut config).unwrap(); assert_eq!( value_m!(multi_config, "config-file", PathBuf), @@ -685,7 +691,6 @@ mod tests { NeighborhoodConfig { mode: NeighborhoodMode::ZeroHop, // not populated on the privileged side min_hops: Hops::ThreeHops, - country: "ZZ".to_string() } ); assert_eq!( @@ -712,7 +717,7 @@ mod tests { vec![Box::new(CommandLineVcl::new(args.into()))]; let multi_config = make_new_multi_config(&app_node(), vcls).unwrap(); - privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut config).unwrap(); + privileged_parse_args(&DirsWrapperReal::default(), &multi_config, &mut config).unwrap(); assert_eq!(None, value_m!(multi_config, "config-file", PathBuf)); assert_eq!( @@ -724,7 +729,7 @@ mod tests { assert!(config.main_cryptde_null_opt.is_none()); assert_eq!( config.real_user, - RealUser::new(None, None, None).populate(&DirsWrapperReal {}) + RealUser::new(None, None, None).populate(&DirsWrapperReal::default()) ); } @@ -740,7 +745,7 @@ mod tests { vec![Box::new(CommandLineVcl::new(args.into()))]; let multi_config = make_new_multi_config(&app_node(), vcls).unwrap(); - privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut config).unwrap(); + privileged_parse_args(&DirsWrapperReal::default(), &multi_config, &mut config).unwrap(); #[cfg(target_os = "linux")] assert_eq!( @@ -766,7 +771,7 @@ mod tests { vec![Box::new(CommandLineVcl::new(args.into()))]; let multi_config = make_new_multi_config(&app_node(), vcls).unwrap(); - privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut config).unwrap(); + privileged_parse_args(&DirsWrapperReal::default(), &multi_config, &mut config).unwrap(); assert_eq!(None, value_m!(multi_config, "config-file", PathBuf)); assert_eq!( @@ -778,7 +783,7 @@ mod tests { assert!(config.main_cryptde_null_opt.is_none()); assert_eq!( config.real_user, - RealUser::new(None, None, None).populate(&DirsWrapperReal {}) + RealUser::new(None, None, None).populate(&DirsWrapperReal::default()) ); } @@ -790,7 +795,7 @@ mod tests { let vcl = Box::new(CommandLineVcl::new(args.into())); let multi_config = make_new_multi_config(&app_node(), vec![vcl]).unwrap(); - privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut config).unwrap(); + privileged_parse_args(&DirsWrapperReal::default(), &multi_config, &mut config).unwrap(); assert_eq!(config.crash_point, CrashPoint::None); } @@ -803,7 +808,7 @@ mod tests { let vcl = Box::new(CommandLineVcl::new(args.into())); let multi_config = make_new_multi_config(&app_node(), vec![vcl]).unwrap(); - privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut config).unwrap(); + privileged_parse_args(&DirsWrapperReal::default(), &multi_config, &mut config).unwrap(); assert_eq!(config.crash_point, CrashPoint::Panic); } @@ -1021,8 +1026,8 @@ mod tests { ); 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); + let config_file = File::create(home_dir.join("config.toml")).unwrap(); + fill_up_config_file(config_file); let env_vec_array = vec![ ( "MASQ_CONFIG_FILE", @@ -1072,16 +1077,26 @@ mod tests { } #[test] - fn server_initializer_collected_params_handle_tilde_in_path_config_file_from_commandline_and_real_user_from_config_file( - ) { + fn tilde_in_config_file_path_from_commandline_and_args_uploaded_from_config_file() { + //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 = home_dir().expect("expectexd home dir"); - 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(); - fill_up_config_file(config_file_relative); + let (node_home_dir, node_home_dir_relative_to_os_home_dir) = + make_node_base_dir_and_return_its_absolute_and_relative_path_to_os_home_dir( + "node_configurator_standard", + "tilde_in_config_file_path_from_commandline_and_real_user_from_config_file", + ); + let data_dir = &node_home_dir.join("data_dir"); + let node_data_dir_relative_to_os_home_dir_str = node_home_dir_relative_to_os_home_dir + .join("data_dir") + .as_os_str() + .to_str() + .unwrap() + .to_string(); + create_dir_all(data_dir).unwrap(); + let config_file = File::create(data_dir.join("config.toml")).unwrap(); + fill_up_config_file(config_file); let env_vec_array = vec![ ("MASQ_BLOCKCHAIN_SERVICE_URL", "https://www.mainnet2.com"), #[cfg(not(target_os = "windows"))] @@ -1094,24 +1109,40 @@ mod tests { #[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"); + .param( + "--config-file", + &format!( + "~/{}/config.toml", + node_data_dir_relative_to_os_home_dir_str + ), + ) + .param( + "--data-directory", + &format!("~/{}", node_data_dir_relative_to_os_home_dir_str), + ); #[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", + &format!( + "~\\{}\\config.toml", + node_data_dir_relative_to_os_home_dir_str + ), + ) + .param( + "--data-directory", + &format!("~\\{}", node_data_dir_relative_to_os_home_dir_str), + ); let args_vec: Vec = args.into(); - let dir_wrapper = DirsWrapperMock::new() - .home_dir_result(Some(home_dir.to_path_buf())) - .data_dir_result(Some(data_dir.to_path_buf())); - let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); - let multiconfig = result.unwrap(); + let multiconfig = + server_initializer_collected_params(&DirsWrapperReal::default(), args_vec.as_slice()) + .unwrap(); assert_eq!( value_m!(multiconfig, "data-directory", String).unwrap(), - data_dir.to_string_lossy().to_string() + data_dir.as_os_str().to_str().unwrap() ); #[cfg(not(target_os = "windows"))] { @@ -1136,12 +1167,11 @@ mod tests { } #[test] - fn server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_data_directory( - ) { + fn config_file_from_env_and_real_user_from_config_file_with_data_directory_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_handle_config_file_from_environment_and_real_user_from_config_file_with_data_directory"); + let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","config_file_from_env_and_real_user_from_config_file_with_data_directory_from_command_line"); let data_dir = &home_dir.join("data_dir"); 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(); @@ -1186,7 +1216,6 @@ 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_fails_on_naked_dir_config_file_without_data_directory"); - let data_dir = &home_dir.join("data_dir"); vec![("MASQ_CONFIG_FILE", "config/config.toml")] .into_iter() @@ -1205,15 +1234,13 @@ mod tests { running_test(); let _guard = EnvironmentGuard::new(); let _clap_guard = ClapGuard::new(); - let home_dir = ensure_node_home_directory_exists( + let node_home_dir = ensure_node_home_directory_exists( "node_configurator_standard", "server_initializer_collected_params_combine_vcls_properly", ); - let data_dir = &home_dir.join("data_dir"); - let config_file = File::create(&home_dir.join("booga.toml")).unwrap(); - let current_directory = current_dir().unwrap(); + let data_dir = &node_home_dir.join("data_dir"); + let config_file = File::create(&node_home_dir.join("booga.toml")).unwrap(); fill_up_config_file(config_file); - let env_vec_array = vec![ ("MASQ_CONFIG_FILE", "booga.toml"), ("MASQ_CLANDESTINE_PORT", "8888"), @@ -1227,10 +1254,13 @@ mod tests { .into_iter() .for_each(|(name, value)| std::env::set_var(name, value)); let dir_wrapper = DirsWrapperMock::new() - .home_dir_result(Some(home_dir.clone())) + .home_dir_result(Some(node_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_vcls_properly/home")).to_string_lossy().to_string().as_str()) + .param( + "--data-directory", + node_home_dir.to_string_lossy().to_string().as_str(), + ) .param("--clandestine-port", "1111") .param("--real-user", "1001:1001:cooga"); let args_vec: Vec = args.into(); @@ -1254,7 +1284,11 @@ mod tests { { assert_eq!( 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() + node_home_dir + .join("booga.toml") + .as_os_str() + .to_str() + .unwrap() ); assert_eq!( value_m!(multiconfig, "real-user", String).unwrap(), @@ -1264,7 +1298,7 @@ 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_vcls_properly/home\\booga.toml").to_string_lossy().to_string() + node_home_dir.to_string_lossy().to_string() ); } @@ -1612,30 +1646,30 @@ mod tests { running_test(); 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"); + let home_dir_poly_amoy = home_dir.join(".local").join("MASQ").join("polygon-amoy"); vec![ (None, None, Some(home_dir_poly_main.to_str().unwrap())), ( - Some("polygon-mumbai"), + Some("polygon-amoy"), None, - Some(home_dir_poly_mumbai.to_str().unwrap()), + Some(home_dir_poly_amoy.to_str().unwrap()), ), (None, Some("/cooga"), Some("/cooga")), - (Some("polygon-mumbai"), Some("/cooga"), Some("/cooga")), + (Some("polygon-amoy"), Some("/cooga"), Some("/cooga")), ( None, - Some("/cooga/polygon-mumbai"), - Some("/cooga/polygon-mumbai"), + Some("/cooga/polygon-amoy"), + Some("/cooga/polygon-amoy"), ), ( None, - Some("/cooga/polygon-mumbai/polygon-mainnet"), - Some("/cooga/polygon-mumbai/polygon-mainnet"), + Some("/cooga/polygon-amoy/polygon-mainnet"), + Some("/cooga/polygon-amoy/polygon-mainnet"), ), ( - Some("polygon-mumbai"), - Some("/cooga/polygon-mumbai"), - Some("/cooga/polygon-mumbai"), + Some("polygon-amoy"), + Some("/cooga/polygon-amoy"), + Some("/cooga/polygon-amoy"), ), ] .iter() diff --git a/node/src/node_configurator/unprivileged_parse_args_configuration.rs b/node/src/node_configurator/unprivileged_parse_args_configuration.rs index c05cb971c..91b0ca81f 100644 --- a/node/src/node_configurator/unprivileged_parse_args_configuration.rs +++ b/node/src/node_configurator/unprivileged_parse_args_configuration.rs @@ -222,11 +222,7 @@ pub fn make_neighborhood_config( }; match make_neighborhood_mode(multi_config, neighbor_configs, persistent_config) { - Ok(mode) => Ok(NeighborhoodConfig { - mode, - min_hops, - country: "ZZ".to_string(), - }), + Ok(mode) => Ok(NeighborhoodConfig { mode, min_hops }), Err(e) => Err(e), } } @@ -722,7 +718,7 @@ mod tests { DEFAULT_RATE_PACK ), min_hops: Hops::OneHop, - country: "ZZ".to_string() + }) ); } @@ -858,7 +854,7 @@ mod tests { Ok(NeighborhoodConfig { mode: NeighborhoodMode::Standard(node_addr, _, _), min_hops: Hops::ThreeHops, - country: _, + .. }) => node_addr, x => panic!("Wasn't expecting {:?}", x), }; diff --git a/node/src/privilege_drop.rs b/node/src/privilege_drop.rs index 1eba9cf88..2b4d8e198 100644 --- a/node/src/privilege_drop.rs +++ b/node/src/privilege_drop.rs @@ -190,7 +190,9 @@ mod tests { let mut subject = PrivilegeDropperReal::new(); subject.id_wrapper = Box::new(id_wrapper); - subject.drop_privileges(&RealUser::new(None, None, None).populate(&DirsWrapperReal {})); + subject.drop_privileges( + &RealUser::new(None, None, None).populate(&DirsWrapperReal::default()), + ); } #[cfg(not(target_os = "windows"))] @@ -283,7 +285,9 @@ mod tests { let mut subject = PrivilegeDropperReal::new(); subject.id_wrapper = Box::new(id_wrapper); - subject.drop_privileges(&RealUser::new(None, None, None).populate(&DirsWrapperReal {})); + subject.drop_privileges( + &RealUser::new(None, None, None).populate(&DirsWrapperReal::default()), + ); let setuid_params = setuid_params_arc.lock().unwrap(); assert!(setuid_params.is_empty()); diff --git a/node/src/proxy_server/mod.rs b/node/src/proxy_server/mod.rs index 058c7c12f..a08985f05 100644 --- a/node/src/proxy_server/mod.rs +++ b/node/src/proxy_server/mod.rs @@ -1123,6 +1123,7 @@ impl IBCDHelper for IBCDHelperReal { neighborhood_sub .send(RouteQueryMessage::data_indefinite_route_request( hostname_opt, + None, payload_size, )) .then(move |route_result| { @@ -1581,7 +1582,11 @@ mod tests { let record = recording.get_record::(0); assert_eq!( record, - &RouteQueryMessage::data_indefinite_route_request(Some("nowhere.com".to_string()), 47) + &RouteQueryMessage::data_indefinite_route_request( + Some("nowhere.com".to_string()), + None, + 47 + ) ); let recording = proxy_server_recording_arc.lock().unwrap(); assert_eq!(recording.len(), 0); @@ -1703,6 +1708,7 @@ mod tests { neighborhood_record, &RouteQueryMessage::data_indefinite_route_request( Some("realdomain.nu".to_string()), + None, 12 ) ); @@ -2095,7 +2101,8 @@ mod tests { target_component: Component::ProxyClient, return_component_opt: Some(Component::ProxyServer), payload_size: 47, - hostname_opt: Some("nowhere.com".to_string()) + hostname_opt: Some("nowhere.com".to_string()), + target_country_opt: None, } ); let dispatcher_recording = dispatcher_log_arc.lock().unwrap(); @@ -2174,7 +2181,8 @@ mod tests { target_component: Component::ProxyClient, return_component_opt: Some(Component::ProxyServer), payload_size: 16, - hostname_opt: None + hostname_opt: None, + target_country_opt: None, } ); let dispatcher_recording = dispatcher_log_arc.lock().unwrap(); @@ -2402,7 +2410,11 @@ mod tests { let record = recording.get_record::(0); assert_eq!( record, - &RouteQueryMessage::data_indefinite_route_request(Some("nowhere.com".to_string()), 47) + &RouteQueryMessage::data_indefinite_route_request( + Some("nowhere.com".to_string()), + None, + 47 + ) ); } @@ -3006,7 +3018,11 @@ mod tests { let record = recording.get_record::(0); assert_eq!( record, - &RouteQueryMessage::data_indefinite_route_request(Some("nowhere.com".to_string()), 47) + &RouteQueryMessage::data_indefinite_route_request( + Some("nowhere.com".to_string()), + None, + 47 + ) ); TestLogHandler::new().exists_log_containing(&format!( "WARN: {test_name}: No route found for hostname: Some(\"nowhere.com\") - stream key {stream_key} - retries left: 3 - AddRouteResultMessage Error: Failed to find route to nowhere.com" @@ -3182,7 +3198,11 @@ mod tests { let record = recording.get_record::(0); assert_eq!( record, - &RouteQueryMessage::data_indefinite_route_request(Some("nowhere.com".to_string()), 47) + &RouteQueryMessage::data_indefinite_route_request( + Some("nowhere.com".to_string()), + None, + 47 + ) ); TestLogHandler::new().exists_log_containing(&format!( "WARN: {test_name}: No route found for hostname: Some(\"nowhere.com\") - stream key {stream_key} - retries left: 3 - AddRouteResultMessage Error: Failed to find route to nowhere.com" diff --git a/node/src/run_modes_factories.rs b/node/src/run_modes_factories.rs index 61a3294ba..c3c009438 100644 --- a/node/src/run_modes_factories.rs +++ b/node/src/run_modes_factories.rs @@ -82,7 +82,7 @@ pub trait DaemonInitializer { impl DumpConfigRunnerFactory for DumpConfigRunnerFactoryReal { fn make(&self) -> Box { Box::new(DumpConfigRunnerReal { - dirs_wrapper: Box::new(DirsWrapperReal), + dirs_wrapper: Box::new(DirsWrapperReal::default()), }) } } @@ -111,7 +111,7 @@ impl DaemonInitializerFactory for DaemonInitializerFactoryReal { impl Default for DIClusteredParams { fn default() -> Self { Self { - dirs_wrapper: Box::new(DirsWrapperReal), + dirs_wrapper: Box::new(DirsWrapperReal::default()), logger_initializer_wrapper: Box::new(LoggerInitializerWrapperReal), channel_factory: Box::new(ChannelFactoryReal::new()), recipients_factory: Box::new(RecipientsFactoryReal::new()), diff --git a/node/src/server_initializer.rs b/node/src/server_initializer.rs index 048e8d6fa..36ba6c17c 100644 --- a/node/src/server_initializer.rs +++ b/node/src/server_initializer.rs @@ -94,7 +94,7 @@ impl Default for ServerInitializerReal { dns_socket_server: Box::new(DnsSocketServer::new()), bootstrapper: Box::new(Bootstrapper::new(Box::new(LoggerInitializerWrapperReal {}))), privilege_dropper: Box::new(PrivilegeDropperReal::new()), - dirs_wrapper: Box::new(DirsWrapperReal), + dirs_wrapper: Box::new(DirsWrapperReal::default()), } } } @@ -394,14 +394,13 @@ pub mod tests { use masq_lib::crash_point::CrashPoint; use masq_lib::multi_config::MultiConfig; use masq_lib::shared_schema::{ConfiguratorError, ParamError}; - use masq_lib::test_utils::fake_stream_holder::{ - ByteArrayReader, ByteArrayWriter, FakeStreamHolder, - }; + use masq_lib::test_utils::fake_stream_holder::FakeStreamHolder; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use masq_lib::utils::slice_of_strs_to_vec_of_strings; use std::cell::RefCell; use std::ops::Not; use std::sync::{Arc, Mutex}; + use test_utilities::byte_array_reader_writer::{ByteArrayReader, ByteArrayWriter}; impl ConfiguredByPrivilege for CrashTestDummy { fn initialize_as_privileged( diff --git a/node/src/sub_lib/neighborhood.rs b/node/src/sub_lib/neighborhood.rs index f2f49afec..955984164 100644 --- a/node/src/sub_lib/neighborhood.rs +++ b/node/src/sub_lib/neighborhood.rs @@ -3,7 +3,7 @@ use crate::neighborhood::gossip::Gossip_0v1; use crate::neighborhood::node_record::NodeRecord; use crate::neighborhood::overall_connection_status::ConnectionProgress; -use crate::neighborhood::Neighborhood; +use crate::neighborhood::{Neighborhood, UserExitPreferences}; use crate::sub_lib::configurator::NewPasswordMessage; use crate::sub_lib::cryptde::{CryptDE, PublicKey}; use crate::sub_lib::cryptde_real::CryptDEReal; @@ -380,6 +380,39 @@ pub enum Hops { SixHops = 6, } +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct ExitLocation { + pub country_codes: Vec, + pub priority: usize, +} + +pub struct ExitLocationSet { + pub locations: Vec, +} + +impl Display for ExitLocation { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Country Codes: {:?}, Priority: {};", + self.country_codes, self.priority + ) + } +} + +impl Display for ExitLocationSet { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + for exit_location in self.locations.iter() { + write!( + f, + "Country Codes: {:?} - Priority: {}; ", + exit_location.country_codes, exit_location.priority + )?; + } + Ok(()) + } +} + impl FromStr for Hops { type Err = String; @@ -406,7 +439,6 @@ impl Display for Hops { pub struct NeighborhoodConfig { pub mode: NeighborhoodMode, pub min_hops: Hops, - pub country: String, } lazy_static! { @@ -478,6 +510,7 @@ pub struct RouteQueryMessage { pub return_component_opt: Option, pub payload_size: usize, pub hostname_opt: Option, + pub target_country_opt: Option, } impl Message for RouteQueryMessage { @@ -487,6 +520,7 @@ impl Message for RouteQueryMessage { impl RouteQueryMessage { pub fn data_indefinite_route_request( hostname_opt: Option, + target_country_opt: Option, payload_size: usize, ) -> RouteQueryMessage { RouteQueryMessage { @@ -495,6 +529,7 @@ impl RouteQueryMessage { return_component_opt: Some(Component::ProxyServer), payload_size, hostname_opt, + target_country_opt, } } } @@ -595,6 +630,7 @@ pub struct NeighborhoodMetadata { pub connection_progress_peers: Vec, pub cpm_recipient: Recipient, pub db_patch_size: u8, + pub user_exit_preferences_opt: Option, } pub struct NeighborhoodTools { @@ -718,12 +754,12 @@ mod tests { } #[test] - fn parse_works_for_mumbai() { - let descriptor = "masq://polygon-mumbai:as45cs5c5@1.2.3.4:4444"; + fn parse_works_for_amoy() { + let descriptor = "masq://polygon-amoy:as45cs5c5@1.2.3.4:4444"; let result = NodeDescriptor::parse_url(descriptor).unwrap(); - assert_eq!(result, (Chain::PolyMumbai, "as45cs5c5", "1.2.3.4:4444")) + assert_eq!(result, (Chain::PolyAmoy, "as45cs5c5", "1.2.3.4:4444")) } #[test] @@ -750,7 +786,7 @@ mod tests { assert_eq!( result, Err( - "Chain identifier 'bitcoin' is not valid; possible values are 'polygon-mainnet', 'eth-mainnet', 'polygon-mumbai', 'eth-ropsten' while formatted as 'masq://:@'" + "Chain identifier 'bitcoin' is not valid; possible values are 'polygon-mainnet', 'eth-mainnet', 'polygon-amoy', 'eth-ropsten' while formatted as 'masq://:@'" .to_string() ) ); @@ -849,7 +885,7 @@ mod tests { let result = DescriptorParsingError::WrongChainIdentifier("blah").to_string(); - assert_eq!(result, "Chain identifier 'blah' is not valid; possible values are 'polygon-mainnet', 'eth-mainnet', 'polygon-mumbai', 'eth-ropsten' while formatted as 'masq://:@'") + assert_eq!(result, "Chain identifier 'blah' is not valid; possible values are 'polygon-mainnet', 'eth-mainnet', 'polygon-amoy', 'eth-ropsten' while formatted as 'masq://:@'") } #[test] @@ -1049,7 +1085,7 @@ mod tests { #[test] fn data_indefinite_route_request() { - let result = RouteQueryMessage::data_indefinite_route_request(None, 7500); + let result = RouteQueryMessage::data_indefinite_route_request(None, None, 7500); assert_eq!( result, @@ -1058,7 +1094,8 @@ mod tests { target_component: Component::ProxyClient, return_component_opt: Some(Component::ProxyServer), payload_size: 7500, - hostname_opt: None + hostname_opt: None, + target_country_opt: None, } ); } diff --git a/node/src/test_utils/mod.rs b/node/src/test_utils/mod.rs index 60a9b7741..5a2ce8edd 100644 --- a/node/src/test_utils/mod.rs +++ b/node/src/test_utils/mod.rs @@ -39,11 +39,14 @@ use crate::sub_lib::sequence_buffer::SequencedPacket; use crate::sub_lib::stream_key::StreamKey; use crate::sub_lib::wallet::Wallet; use crossbeam_channel::{unbounded, Receiver, Sender}; +use dirs::home_dir; use ethsign_crypto::Keccak256; use futures::sync::mpsc::SendError; use lazy_static::lazy_static; use masq_lib::constants::HTTP_PORT; -use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN; +use masq_lib::test_utils::utils::{ + ensure_node_home_directory_exists, node_home_directory, TEST_DEFAULT_CHAIN, +}; use rand::RngCore; use regex::Regex; use rustc_hex::ToHex; @@ -51,12 +54,14 @@ use serde_derive::{Deserialize, Serialize}; use std::collections::btree_set::BTreeSet; use std::collections::HashSet; use std::convert::From; +use std::env::current_dir; use std::fmt::Debug; use std::hash::Hash; use std::io::ErrorKind; use std::io::Read; use std::iter::repeat; use std::net::{Shutdown, TcpStream}; +use std::path::PathBuf; use std::str::FromStr; use std::sync::{Arc, Mutex}; use std::thread; @@ -516,7 +521,7 @@ pub fn assert_eq_debug(a: T, b: T) { assert_eq!(a_str, b_str); } -//must stay without cfg(test) -- used in another crate +// Must stay without cfg(test) -- used in another crate #[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)] pub struct TestRawTransaction { pub nonce: U256, @@ -529,6 +534,20 @@ pub struct TestRawTransaction { pub data: Vec, } +pub fn make_node_base_dir_and_return_its_absolute_and_relative_path_to_os_home_dir( + module: &str, + name: &str, +) -> (PathBuf, PathBuf) { + let node_base_dir_relative = ensure_node_home_directory_exists(module, name); + let home_dir_path = home_dir().unwrap(); + let current_dir = current_dir().unwrap(); + let current_dir_tilde_like_path = current_dir.strip_prefix(home_dir_path).unwrap(); + let node_base_dir_tilde_path = + current_dir_tilde_like_path.join(node_home_directory(module, name)); + let node_base_dir_absolute = current_dir.join(node_base_dir_relative); + (node_base_dir_absolute, node_base_dir_tilde_path) +} + #[macro_export] macro_rules! arbitrary_id_stamp_in_trait { () => { diff --git a/node/src/test_utils/neighborhood_test_utils.rs b/node/src/test_utils/neighborhood_test_utils.rs index 72545797a..983fd7932 100644 --- a/node/src/test_utils/neighborhood_test_utils.rs +++ b/node/src/test_utils/neighborhood_test_utils.rs @@ -66,13 +66,22 @@ impl From<(&NeighborhoodDatabase, &PublicKey, bool)> for AccessibleGossipRecord } } -pub fn make_node_record(n: u16, has_ip: bool) -> NodeRecord { +pub fn make_segments(n: u16) -> (u8, u8, u8, u8) { let seg1 = ((n / 1000) % 10) as u8; let seg2 = ((n / 100) % 10) as u8; let seg3 = ((n / 10) % 10) as u8; let seg4 = (n % 10) as u8; + (seg1, seg2, seg3, seg4) +} + +pub fn make_segmented_ip(seg1: u8, seg2: u8, seg3: u8, seg4: u8) -> IpAddr { + IpAddr::V4(Ipv4Addr::new(seg1, seg2, seg3, seg4)) +} + +pub fn make_node_record(n: u16, has_ip: bool) -> NodeRecord { + let (seg1, seg2, seg3, seg4) = make_segments(n); let key = PublicKey::new(&[seg1, seg2, seg3, seg4]); - let ip_addr = IpAddr::V4(Ipv4Addr::new(seg1, seg2, seg3, seg4)); + let ip_addr = make_segmented_ip(seg1, seg2, seg3, seg4); let node_addr = NodeAddr::new(&ip_addr, &[n % 10000]); let (_ip, country_code, free_world_bit) = pick_country_code_record(n % 6); let location_opt = match country_code.is_empty() { @@ -148,12 +157,10 @@ pub fn neighborhood_from_nodes( *root.rate_pack(), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, None => NeighborhoodConfig { mode: NeighborhoodMode::ZeroHop, min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, }; config.earning_wallet = root.earning_wallet(); diff --git a/node/tests/contract_test.rs b/node/tests/contract_test.rs index 6002f392b..8e9e74395 100644 --- a/node/tests/contract_test.rs +++ b/node/tests/contract_test.rs @@ -105,14 +105,13 @@ where } #[test] -fn masq_erc20_contract_exists_on_polygon_mumbai_integration() { +fn masq_erc20_contract_exists_on_polygon_amoy_integration() { let blockchain_urls = vec![ - "https://rpc-mumbai.polygon.technology", - "https://matic-mumbai.chainstacklabs.com", - "https://rpc-mumbai.maticvigil.com", - "https://matic-testnet-archive-rpc.bwarelabs.com", + "https://rpc-amoy.polygon.technology", + "https://rpc.ankr.com/polygon_amoy", + "https://80002.rpc.thirdweb.com", ]; - let chain = Chain::PolyMumbai; + let chain = Chain::PolyAmoy; let assertion_body = |url, chain| assert_contract_existence(url, chain, "tMASQ", 18); assert_contract(blockchain_urls, &chain, assertion_body) diff --git a/node/tests/dump_configuration_test.rs b/node/tests/dump_configuration_test.rs index 044eb2043..62a045b0d 100644 --- a/node/tests/dump_configuration_test.rs +++ b/node/tests/dump_configuration_test.rs @@ -22,7 +22,7 @@ fn dump_configuration_with_an_existing_database_integration() { Some( CommandConfig::new() .pair("--ui-port", &port.to_string()) - .pair("--chain", "polygon-mumbai"), + .pair("--chain", "polygon-amoy"), ), true, true, @@ -38,7 +38,7 @@ fn dump_configuration_with_an_existing_database_integration() { let mut node = utils::MASQNode::run_dump_config( test_name, - Some(CommandConfig::new().pair("--chain", "polygon-mumbai")), + Some(CommandConfig::new().pair("--chain", "polygon-amoy")), false, true, true, diff --git a/node/tests/node_exits_from_future_panic_test.rs b/node/tests/node_exits_from_future_panic_test.rs index 1419dc0fe..dc3ec25ab 100644 --- a/node/tests/node_exits_from_future_panic_test.rs +++ b/node/tests/node_exits_from_future_panic_test.rs @@ -67,7 +67,7 @@ const STAT_FORMAT_PARAM_NAME: &str = "-f"; fn node_logfile_does_not_belong_to_root_integration() { let mut node = MASQNode::start_standard( "node_logfile_does_not_belong_to_root_integration", - Some(CommandConfig::new().pair("--chain", "polygon-mumbai")), + Some(CommandConfig::new().pair("--chain", "polygon-amoy")), true, true, false, diff --git a/test_utilities/Cargo.toml b/test_utilities/Cargo.toml new file mode 100644 index 000000000..93191e691 --- /dev/null +++ b/test_utilities/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "test_utilities" +version = "0.1.0" +edition = "2021" +authors = ["Dan Wiebe ", "MASQ"] +license = "GPL-3.0-only" +description = "Testing utilities Code common to Node and masq; also, temporarily, to dns_utility" +workspace = "../node" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] \ No newline at end of file diff --git a/test_utilities/src/byte_array_reader_writer.rs b/test_utilities/src/byte_array_reader_writer.rs new file mode 100644 index 000000000..fe611f781 --- /dev/null +++ b/test_utilities/src/byte_array_reader_writer.rs @@ -0,0 +1,132 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use std::cmp::min; +use std::io; +use std::io::Read; +use std::io::Write; +use std::io::{BufRead, Error}; +use std::sync::{Arc, Mutex}; + +pub struct ByteArrayWriter { + inner_arc: Arc>, +} + +pub struct ByteArrayWriterInner { + byte_array: Vec, + next_error: Option, +} + +impl ByteArrayWriterInner { + pub fn get_bytes(&self) -> Vec { + self.byte_array.clone() + } + pub fn get_string(&self) -> String { + String::from_utf8(self.get_bytes()).unwrap() + } +} + +impl Default for ByteArrayWriter { + fn default() -> Self { + ByteArrayWriter { + inner_arc: Arc::new(Mutex::new(ByteArrayWriterInner { + byte_array: vec![], + next_error: None, + })), + } + } +} + +impl ByteArrayWriter { + pub fn new() -> ByteArrayWriter { + Self::default() + } + + pub fn inner_arc(&self) -> Arc> { + self.inner_arc.clone() + } + + pub fn get_bytes(&self) -> Vec { + self.inner_arc.lock().unwrap().byte_array.clone() + } + pub fn get_string(&self) -> String { + String::from_utf8(self.get_bytes()).unwrap() + } + + pub fn reject_next_write(&mut self, error: Error) { + self.inner_arc().lock().unwrap().next_error = Some(error); + } +} + +impl Write for ByteArrayWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + let mut inner = self.inner_arc.lock().unwrap(); + if let Some(next_error) = inner.next_error.take() { + Err(next_error) + } else { + for byte in buf { + inner.byte_array.push(*byte) + } + Ok(buf.len()) + } + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +pub struct ByteArrayReader { + byte_array: Vec, + position: usize, + next_error: Option, +} + +impl ByteArrayReader { + pub fn new(byte_array: &[u8]) -> ByteArrayReader { + ByteArrayReader { + byte_array: byte_array.to_vec(), + position: 0, + next_error: None, + } + } + + pub fn reject_next_read(mut self, error: Error) -> ByteArrayReader { + self.next_error = Some(error); + self + } +} + +impl Read for ByteArrayReader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match self.next_error.take() { + Some(error) => Err(error), + None => { + let to_copy = min(buf.len(), self.byte_array.len() - self.position); + #[allow(clippy::needless_range_loop)] + for idx in 0..to_copy { + buf[idx] = self.byte_array[self.position + idx] + } + self.position += to_copy; + Ok(to_copy) + } + } + } +} + +impl BufRead for ByteArrayReader { + fn fill_buf(&mut self) -> io::Result<&[u8]> { + match self.next_error.take() { + Some(error) => Err(error), + None => Ok(&self.byte_array[self.position..]), + } + } + + fn consume(&mut self, amt: usize) { + let result = self.position + amt; + self.position = if result < self.byte_array.len() { + result + } else { + self.byte_array.len() + } + } +} diff --git a/test_utilities/src/lib.rs b/test_utilities/src/lib.rs new file mode 100644 index 000000000..552c3b7d1 --- /dev/null +++ b/test_utilities/src/lib.rs @@ -0,0 +1,3 @@ +// Copyright (c) 2024, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +pub mod byte_array_reader_writer;