diff --git a/USER-INTERFACE-INTERFACE.md b/USER-INTERFACE-INTERFACE.md index c7059929a..134c6ae64 100644 --- a/USER-INTERFACE-INTERFACE.md +++ b/USER-INTERFACE-INTERFACE.md @@ -590,6 +590,67 @@ descriptor (for example, if it's still waiting on the router to get a public IP Node descriptor (for example, if its neighborhood mode is not Standard), the `nodeDescriptorOpt` field will be null or absent. +#### `exit-location` +##### Direction: Request +##### Correspondent: Node +##### Layout: +``` +"payload": { + "fallbackRouting": , + "exitLocations": [ + { + "countryCodes": [string, ..], + "priority": + }, + ], + "showCountries": +} +``` +##### Description: +This command requests information about the countries available for exit in our neighborhood and allows us to set up the +desired locations with their priority. The priority provides the node's perspective on how important a particular country +is for our preferences. + +This command can be used in two ways which can't be combined: +1. If we use the command with showCountries set to true, it retrieves information about the available countries in our neighborhood. In this case, other parameters are ignored. +2. If we want to set an exit location, we must set showCountries to false and then configure fallbackRouting and exitLocations with our preferences. + +The fallbackRouting parameter determines whether we want to block exit for a particular country. If this country is no longer +available, the route to exit will fail during construction. If fallbackRouting is set to true, we can exit through any available +country if none of our specified exitLocations are accessible. + +Priorities are used to determine the preferred exit countries. Priority 1 is the highest, while higher numbers indicate +lower priority. For example, if we specify DE with priority 1 and FR with priority 2, then an exit through France will +only be used if a German exit is unavailable or significantly more expensive. + +#### `exit-location` +##### Direction: Response +##### Correspondent: UI +##### Layout: + +``` +"payload": { + "fallbackRouting": , + "exitCountrySelection": <[ + { + "countryCodes": [string, ..], + "priority": + }, + ]>, + "missingCountries": <[string, ..]> + "exitCountries": +} +``` +##### Description: +In response, the Node sends a payload to the UI that contains either the Exit Location settings (which may include missing countries) or a list of exit countries. + +Exit Location settings consist of fallbackRouting, exitCountrySelection, and missingCountries, where: +1. fallbackRouting is a boolean representing the user's choice to enable or disable fallback routing within the neighborhood. +2. exitCountrySelection is an array of objects, where each object represents a set of country codes along with their assigned priority. +3. missingCountries is an array of strings representing a list of countries that are currently unavailable in the Node's Neighborhood Database. + +Exit Countries (or exitCountries) is an optional array containing ISO country code strings. These represent the countries currently available in the Node's Neighborhood Database. The user can select from these countries to configure the Exit Location settings. + #### `financials` ##### Direction: Request ##### Correspondent: Node diff --git a/ip_country/src/ip_country.rs b/ip_country/src/ip_country.rs index e2fb0651b..cfd335150 100644 --- a/ip_country/src/ip_country.rs +++ b/ip_country/src/ip_country.rs @@ -7,7 +7,6 @@ use std::io; const COUNTRY_BLOCK_BIT_SIZE: usize = 64; -#[allow(unused_must_use)] pub fn ip_country( _args: Vec, stdin: &mut dyn io::Read, @@ -52,8 +51,9 @@ pub fn ip_country( *** DO NOT USE THIS CODE *** "#, error_list - ); - write!(stderr, "{}", error_list); + ) + .expect("expected WANRNING output"); + write!(stderr, "{}", error_list).expect("expected error list output"); 1 } } diff --git a/masq/src/command_factory.rs b/masq/src/command_factory.rs index 6ec658903..12515027b 100644 --- a/masq/src/command_factory.rs +++ b/masq/src/command_factory.rs @@ -108,7 +108,7 @@ impl CommandFactoryReal { mod tests { use super::*; use crate::command_factory::CommandFactoryError::UnrecognizedSubcommand; - use masq_lib::messages::CountryCodes; + use masq_lib::messages::CountryGroups; #[test] fn complains_about_unrecognized_subcommand() { @@ -282,7 +282,7 @@ mod tests { .downcast_ref::() .unwrap(), &SetExitLocationCommand { - exit_locations: vec![CountryCodes { + exit_locations: vec![CountryGroups { country_codes: vec!["CZ".to_string()], priority: 1 }], diff --git a/masq/src/commands/exit_location_command.rs b/masq/src/commands/exit_location_command.rs index 15cb329f6..ed4687fd4 100644 --- a/masq/src/commands/exit_location_command.rs +++ b/masq/src/commands/exit_location_command.rs @@ -5,11 +5,10 @@ use crate::commands::commands_common::CommandError::Payload; use crate::commands::commands_common::{ transaction, Command, CommandError, STANDARD_COMMAND_TIMEOUT_MILLIS, }; -use clap::{App, Arg, SubCommand}; -use masq_lib::constants::{EXIT_COUNTRY_ERROR, EXIT_COUNTRY_MISSING_COUNTRIES_ERROR}; -use masq_lib::messages::{ - CountryCodes, ExitLocationSet, UiSetExitLocationRequest, UiSetExitLocationResponse, -}; +use clap::{App, Arg, ArgGroup, SubCommand}; +use masq_lib::constants::EXIT_COUNTRY_MISSING_COUNTRIES_ERROR; +use masq_lib::exit_locations::ExitLocationSet; +use masq_lib::messages::{CountryGroups, UiSetExitLocationRequest, UiSetExitLocationResponse}; use masq_lib::shared_schema::common_validators; use masq_lib::{as_any_ref_in_trait_impl, short_writeln}; use std::fmt::Debug; @@ -40,7 +39,7 @@ const FALLBACK_ROUTING_HELP: &str = "If you just want to make a suggestion, and 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"; -const SHOW_COUNTRIES_HELP: &str = "To display all country codes available for exit in Database, use this flag without anything else: \n\n\ +const SHOW_COUNTRIES_HELP: &str = "Use this flag to display all country codes available for exit Nodes in your Neighborhood: \n\n\ masq> exit-location --show-countries "; pub fn exit_location_subcommand() -> App<'static, 'static> { @@ -49,7 +48,7 @@ pub fn exit_location_subcommand() -> App<'static, 'static> { #[derive(Debug, PartialEq, Eq)] pub struct SetExitLocationCommand { - pub exit_locations: Vec, + pub exit_locations: Vec, pub fallback_routing: bool, pub show_countries: bool, } @@ -64,7 +63,7 @@ impl SetExitLocationCommand { .expect("Expected Country Codes") .into_iter() .enumerate() - .map(|(index, code)| CountryCodes::from((code.to_string(), index))) + .map(|(index, code)| CountryGroups::from((code.to_string(), index))) .collect(), false => vec![], }; @@ -111,14 +110,15 @@ impl Command for SetExitLocationCommand { "No countries available for exit-location!" ), } + } else { + match exit_location_response.fallback_routing { + true => short_writeln!(context.stdout(), "Fallback Routing is set.",), + false => short_writeln!(context.stdout(), "Fallback Routing NOT set.",), + } } - match exit_location_response.fallback_routing { - true => short_writeln!(context.stdout(), "Fallback Routing is set.",), - false => short_writeln!(context.stdout(), "Fallback Routing NOT set.",), - } - if !exit_location_response.exit_locations.is_empty() { + if !exit_location_response.exit_country_selection.is_empty() { let location_set = ExitLocationSet { - locations: exit_location_response.exit_locations, + locations: exit_location_response.exit_country_selection, }; if !exit_location_response.missing_countries.is_empty() { short_writeln!( @@ -135,16 +135,12 @@ impl Command for SetExitLocationCommand { } short_writeln!(context.stdout(), "Exit location set: {}", location_set); } else if exit_location_response.fallback_routing - && exit_location_response.exit_locations.is_empty() + && exit_location_response.exit_country_selection.is_empty() { short_writeln!(context.stdout(), "Exit location is Unset."); } Ok(()) } - Err(Payload(code, message)) if code == EXIT_COUNTRY_ERROR => { - short_writeln!(context.stderr(), "Error: Something went wrong!"); - Err(Payload(code, message)) - } Err(Payload(code, message)) => { short_writeln!(context.stderr(), "code: {}\nmessage: {}", code, message); if code == EXIT_COUNTRY_MISSING_COUNTRIES_ERROR { @@ -194,6 +190,13 @@ pub fn set_exit_location_subcommand() -> App<'static, 'static> { .takes_value(false) .required(false), ) + .group( + ArgGroup::with_name("show-countries-fallback") + .args(&["show-countries", "fallback-routing"]), + ) + .group( + ArgGroup::with_name("show-countries-ccodes").args(&["show-countries", "country-codes"]), + ) } #[cfg(test)] @@ -205,9 +208,9 @@ pub mod tests { Command, CommandError, STANDARD_COMMAND_TIMEOUT_MILLIS, }; use crate::test_utils::mocks::CommandContextMock; - use masq_lib::constants::{EXIT_COUNTRY_ERROR, EXIT_COUNTRY_MISSING_COUNTRIES_ERROR}; + use masq_lib::constants::EXIT_COUNTRY_MISSING_COUNTRIES_ERROR; use masq_lib::messages::{ - CountryCodes, ExitLocation, ToMessageBody, UiSetExitLocationRequest, + CountryGroups, ExitLocation, ToMessageBody, UiSetExitLocationRequest, UiSetExitLocationResponse, }; use std::sync::{Arc, Mutex}; @@ -246,7 +249,7 @@ pub mod tests { ); assert_eq!( SHOW_COUNTRIES_HELP, - "To display all country codes available for exit in Database, use this flag without anything else: \n\n\ + "Use this flag to display all country codes available for exit Nodes in your Neighborhood: \n\n\ masq> exit-location --show-countries " ); } @@ -270,67 +273,44 @@ pub mod tests { ); assert_eq!( stderr.lock().unwrap().get_string(), - "code: 9223372036854775817\nmessage: \"CZ, SK, IN\"\n".to_string() + "code: 9223372036854775816\nmessage: \"CZ, SK, IN\"\n".to_string() ); assert_eq!( result, Err(CommandError::Payload( - 9223372036854775817, + 9223372036854775816, "\"CZ, SK, IN\"".to_string() )) ); } - #[test] - fn testing_exit_location_general_error() { - let factory = CommandFactoryReal::new(); - let mut context = CommandContextMock::new().transact_result(Err( - ContextError::PayloadError(EXIT_COUNTRY_ERROR, "".to_string()), - )); - let subject = factory.make(&["exit-location".to_string()]).unwrap(); - - let result = subject.execute(&mut context); - let stderr = context.stderr_arc(); - let stdout = context.stdout_arc(); - assert!(stdout.lock().unwrap().get_string().is_empty()); - assert_eq!( - stderr.lock().unwrap().get_string(), - "Error: Something went wrong!\n".to_string() - ); - assert_eq!(result, Err(Payload(9223372036854775816, "".to_string()))); - } - #[test] fn testing_handler_for_exit_location_responose() { let message_body = Ok(UiSetExitLocationResponse { - fallback_routing: false, - exit_locations: vec![ - ExitLocation { - country_codes: vec!["CZ".to_string()], - priority: 1, - }, - ExitLocation { - country_codes: vec!["FR".to_string()], - priority: 2, - }, - ], - exit_countries: Some(vec!["FR".to_string()]), - missing_countries: vec!["CZ".to_string()], + fallback_routing: true, + exit_country_selection: vec![], + exit_countries: None, + missing_countries: vec![], } .tmb(1234)); let factory = CommandFactoryReal::new(); let mut context = CommandContextMock::new().transact_result(message_body); - let subject = factory.make(&["exit-location".to_string()]).unwrap(); + let subject = factory + .make(&[ + "exit-location".to_string(), + "--fallback-routing".to_string(), + ]) + .unwrap(); let result = subject.execute(&mut context); let stderr = context.stderr_arc(); let stdout = context.stdout_arc(); - assert_eq!(stdout.lock().unwrap().get_string(), "Countries available for exit-location: [\"FR\"]\nFallback Routing NOT set.\nFollowing countries are missing in Database: [\"CZ\"]\nExit location set: Country Codes: [\"CZ\"] - Priority: 1; Country Codes: [\"FR\"] - Priority: 2; \n".to_string()); assert_eq!( - stderr.lock().unwrap().get_string(), - "code: 9223372036854775817\nmessage: [\"CZ\"]\n".to_string() + stdout.lock().unwrap().get_string(), + "Fallback Routing is set.\nExit location is Unset.\n".to_string() ); + assert_eq!(stderr.lock().unwrap().get_string(), "".to_string()); assert_eq!(result, Ok(())); } @@ -341,7 +321,7 @@ pub mod tests { .transact_params(&transact_params_arc) .transact_result(Ok(UiSetExitLocationResponse { fallback_routing: true, - exit_locations: vec![ + exit_country_selection: vec![ ExitLocation { country_codes: vec!["CZ".to_string(), "SK".to_string()], priority: 1, @@ -375,15 +355,15 @@ pub mod tests { let expected_request = UiSetExitLocationRequest { fallback_routing: true, exit_locations: vec![ - CountryCodes { + CountryGroups { country_codes: vec!["CZ".to_string(), "SK".to_string()], priority: 1, }, - CountryCodes { + CountryGroups { country_codes: vec!["AT".to_string(), "DE".to_string()], priority: 2, }, - CountryCodes { + CountryGroups { country_codes: vec!["PL".to_string()], priority: 3, }, @@ -397,7 +377,7 @@ pub mod tests { &[(expected_message_body, STANDARD_COMMAND_TIMEOUT_MILLIS)] ); let stdout = stdout_arc.lock().unwrap(); - assert_eq!(&stdout.get_string(), "Fallback Routing is set.\nExit location set: Country Codes: [\"CZ\", \"SK\"] - Priority: 1; Country Codes: [\"AT\", \"DE\"] - Priority: 2; Country Codes: [\"PL\"] - Priority: 3; \n"); + assert_eq!(&stdout.get_string(), "Fallback Routing is set.\nExit location set: Country Codes: [\"CZ\", \"SK\"] - Priority: 1; Country Codes: [\"AT\", \"DE\"] - Priority: 2; Country Codes: [\"PL\"] - Priority: 3\n"); let stderr = stderr_arc.lock().unwrap(); assert_eq!(&stderr.get_string(), ""); } @@ -409,7 +389,7 @@ pub mod tests { .transact_params(&transact_params_arc) .transact_result(Ok(UiSetExitLocationResponse { fallback_routing: false, - exit_locations: vec![ExitLocation { + exit_country_selection: vec![ExitLocation { country_codes: vec!["CZ".to_string()], priority: 1, }], @@ -431,7 +411,7 @@ pub mod tests { assert_eq!(result, Ok(())); let expected_request = UiSetExitLocationRequest { fallback_routing: false, - exit_locations: vec![CountryCodes { + exit_locations: vec![CountryGroups { country_codes: vec!["CZ".to_string()], priority: 1, }], @@ -445,57 +425,35 @@ pub mod tests { ); let stdout = stdout_arc.lock().unwrap(); let stderr = stderr_arc.lock().unwrap(); - assert_eq!(&stdout.get_string(), "Fallback Routing NOT set.\nExit location set: Country Codes: [\"CZ\"] - Priority: 1; \n"); + assert_eq!( + &stdout.get_string(), + "Fallback Routing NOT set.\nExit location set: Country Codes: [\"CZ\"] - Priority: 1\n" + ); 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 { - fallback_routing: true, - exit_locations: vec![], - exit_countries: None, - missing_countries: vec![], - } - .tmb(0))); - let stderr_arc = context.stderr_arc(); - let stdout_arc = context.stdout_arc(); - let subject = SetExitLocationCommand::new(&["exit-location".to_string()]).unwrap(); + let result = 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(), ""); - let stdout = stdout_arc.lock().unwrap(); assert_eq!( - &stdout.get_string(), - "Fallback Routing is set.\nExit location is Unset.\n" + result, + SetExitLocationCommand { + exit_locations: vec![], + fallback_routing: true, + show_countries: false + } ); } #[test] - fn providing_show_countries_cause_request_for_list_of_countries() { + fn providing_show_countries_flag_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 { fallback_routing: false, - exit_locations: vec![], + exit_country_selection: vec![], exit_countries: Some(vec!["CZ".to_string()]), missing_countries: vec![], } @@ -527,7 +485,29 @@ pub mod tests { let stdout = stdout_arc.lock().unwrap(); assert_eq!( &stdout.get_string(), - "Countries available for exit-location: [\"CZ\"]\nFallback Routing NOT set.\n" + "Countries available for exit-location: [\"CZ\"]\n" + ); + } + + #[test] + fn providing_show_countries_with_other_argument_fails() { + let result_expected_one = "SetExitLocationCommand error: The argument '--country-codes ' cannot be used with one or more of the other specified arguments\n\nUSAGE:\n"; + let result_expected_two = "SetExitLocationCommand error: The argument '--show-countries' cannot be used with one or more of the other specified arguments\n\nUSAGE:\n"; + + let result = SetExitLocationCommand::new(&[ + "exit-location".to_string(), + "--show-countries".to_string(), + "--country-codes".to_string(), + "CZ".to_string(), + ]) + .unwrap_err(); + + assert!( + result.contains(result_expected_one) || result.contains(result_expected_two), + "result was {:?}, but expected {:?} or {:?}", + result, + result_expected_one, + result_expected_two ); } } diff --git a/masq_lib/src/constants.rs b/masq_lib/src/constants.rs index a073232b8..732968ec1 100644 --- a/masq_lib/src/constants.rs +++ b/masq_lib/src/constants.rs @@ -26,6 +26,8 @@ pub const WEIS_IN_GWEI: i128 = 1_000_000_000; pub const DEFAULT_MAX_BLOCK_COUNT: u64 = 100_000; +pub const PAYLOAD_ZERO_SIZE: usize = 0usize; + pub const ETH_MAINNET_CONTRACT_CREATION_BLOCK: u64 = 11_170_708; pub const ETH_ROPSTEN_CONTRACT_CREATION_BLOCK: u64 = 8_688_171; pub const POLYGON_MAINNET_CONTRACT_CREATION_BLOCK: u64 = 14_863_650; @@ -75,8 +77,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; -pub const EXIT_COUNTRY_MISSING_COUNTRIES_ERROR: u64 = UI_NODE_COMMUNICATION_PREFIX | 9; +pub const EXIT_COUNTRY_MISSING_COUNTRIES_ERROR: u64 = UI_NODE_COMMUNICATION_PREFIX | 8; //accountant pub const ACCOUNTANT_PREFIX: u64 = 0x0040_0000_0000_0000; @@ -106,6 +107,9 @@ pub const BASE_MAINNET_FULL_IDENTIFIER: &str = concatcp!(BASE_FAMILY, LINK, MAIN pub const BASE_SEPOLIA_FULL_IDENTIFIER: &str = concatcp!(BASE_FAMILY, LINK, "sepolia"); pub const DEV_CHAIN_FULL_IDENTIFIER: &str = "dev"; +//allocations +pub const DEFAULT_PREALLOCATION_VEC: usize = 10; + #[cfg(test)] mod tests { use super::*; @@ -200,6 +204,8 @@ mod tests { NODE_RECORD_INNER_CURRENT_VERSION, DataVersion { major: 0, minor: 1 } ); + assert_eq!(PAYLOAD_ZERO_SIZE, 0usize); + assert_eq!(DEFAULT_PREALLOCATION_VEC, 10) } #[test] diff --git a/masq_lib/src/exit_locations.rs b/masq_lib/src/exit_locations.rs new file mode 100644 index 000000000..c5884be54 --- /dev/null +++ b/masq_lib/src/exit_locations.rs @@ -0,0 +1,27 @@ +// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::messages::ExitLocation; +use itertools::Itertools; +use std::fmt::{Display, Formatter}; + +pub struct ExitLocationSet { + pub locations: Vec, +} + +impl Display for ExitLocationSet { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + let exit_location_string = self + .locations + .iter() + .map(|exit_location| { + format!( + "Country Codes: {:?} - Priority: {}", + exit_location.country_codes, exit_location.priority + ) + }) + .collect_vec() + .join("; "); + write!(f, "{}", exit_location_string)?; + Ok(()) + } +} diff --git a/masq_lib/src/lib.rs b/masq_lib/src/lib.rs index f8e72a956..1fc5eb68d 100644 --- a/masq_lib/src/lib.rs +++ b/masq_lib/src/lib.rs @@ -22,6 +22,7 @@ pub mod command; pub mod constants; pub mod crash_point; pub mod data_version; +pub mod exit_locations; pub mod shared_schema; pub mod test_utils; pub mod type_obfuscation; diff --git a/masq_lib/src/messages.rs b/masq_lib/src/messages.rs index 86d8b083b..f8c6effdc 100644 --- a/masq_lib/src/messages.rs +++ b/masq_lib/src/messages.rs @@ -848,18 +848,19 @@ pub struct UiWalletAddressesResponse { } conversation_message!(UiWalletAddressesResponse, "walletAddresses"); +// CountryGroups are inbound data for ExitLocations from UI. These data structures could be enriched +// in the future according to future user interface needs of more specification #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct CountryCodes { - #[serde(rename = "countryCodes")] +pub struct CountryGroups { + #[serde(rename = "CountryGroups")] 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 +impl From<(String, usize)> for CountryGroups { + fn from((country, priority): (String, usize)) -> Self { + CountryGroups { + country_codes: country .split(',') .into_iter() .map(|x| x.to_string()) @@ -874,7 +875,7 @@ pub struct UiSetExitLocationRequest { #[serde(rename = "fallbackRouting")] pub fallback_routing: bool, #[serde(rename = "exitLocations")] - pub exit_locations: Vec, + pub exit_locations: Vec, #[serde(rename = "showCountries")] pub show_countries: bool, } @@ -882,6 +883,7 @@ conversation_message!(UiSetExitLocationRequest, "exitLocation"); #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct ExitLocation { + #[serde(rename = "CountryGroups")] pub country_codes: Vec, pub priority: usize, } @@ -896,29 +898,12 @@ impl Display for ExitLocation { } } -pub struct ExitLocationSet { - pub locations: Vec, -} - -impl Display for ExitLocationSet { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - for exit_location in self.locations.iter() { - write!( - f, - "Country Codes: {:?} - Priority: {}; ", - exit_location.country_codes, exit_location.priority - )?; - } - Ok(()) - } -} - #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct UiSetExitLocationResponse { #[serde(rename = "fallbackRouting")] pub fallback_routing: bool, #[serde(rename = "exitCountrySelection")] - pub exit_locations: Vec, + pub exit_country_selection: Vec, #[serde(rename = "exitCountries")] pub exit_countries: Option>, #[serde(rename = "missingCountries")] diff --git a/masq_lib/src/shared_schema.rs b/masq_lib/src/shared_schema.rs index 9bd948e8d..46c01fe14 100644 --- a/masq_lib/src/shared_schema.rs +++ b/masq_lib/src/shared_schema.rs @@ -537,16 +537,16 @@ pub mod common_validators { pub fn validate_exit_locations(exit_location: String) -> Result<(), String> { validate_pipe_separated_values(exit_location, |country: String| { - let mut collect_fails = "".to_string(); + let mut collect_fails = vec![]; country.split(',').into_iter().for_each(|country_code| { match validate_country_code(country_code) { Ok(_) => (), - Err(e) => collect_fails.push_str(&e), + Err(e) => collect_fails.push(e), } }); match collect_fails.is_empty() { true => Ok(()), - false => Err(collect_fails), + false => Err(collect_fails.join(", ")), } }) } @@ -651,14 +651,14 @@ pub mod common_validators { fn validate_pipe_separated_values( values_with_delimiters: String, - closure: fn(String) -> Result<(), String>, + parse_value: 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()) { + match parse_value(segment.to_string()) { Ok(_) => (), Err(msg) => error_collection.push(msg), }; @@ -980,11 +980,11 @@ 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")); + let result = common_validators::validate_exit_locations(String::from("CZ|SK,RR,XP")); assert_eq!( result, - Err("'RR' is not a valid ISO3166 country code".to_string()) + Err("'RR' is not a valid ISO3166 country code, 'XP' is not a valid ISO3166 country code".to_string()) ); } diff --git a/multinode_integration_tests/src/masq_mock_node.rs b/multinode_integration_tests/src/masq_mock_node.rs index 801c694d1..384209e20 100644 --- a/multinode_integration_tests/src/masq_mock_node.rs +++ b/multinode_integration_tests/src/masq_mock_node.rs @@ -147,6 +147,10 @@ impl MASQNode for MASQMockNode { fn routes_data(&self) -> bool { true // just a guess } + + fn country_code_opt(&self) -> Option { + None + } } pub struct MutableMASQMockNode { diff --git a/multinode_integration_tests/src/masq_node.rs b/multinode_integration_tests/src/masq_node.rs index 1e2528ed8..20bc4fb49 100644 --- a/multinode_integration_tests/src/masq_node.rs +++ b/multinode_integration_tests/src/masq_node.rs @@ -7,6 +7,7 @@ use masq_lib::constants::{ MASQ_URL_PREFIX, }; use masq_lib::utils::to_string; +use node_lib::neighborhood::node_location::get_node_location; use node_lib::sub_lib::cryptde::{CryptDE, PublicKey}; use node_lib::sub_lib::cryptde_null::CryptDENull; use node_lib::sub_lib::neighborhood::{NodeDescriptor, RatePack}; @@ -210,6 +211,7 @@ pub trait MASQNode: Any { fn chain(&self) -> Chain; fn accepts_connections(&self) -> bool; fn routes_data(&self) -> bool; + fn country_code_opt(&self) -> Option; } pub struct MASQNodeUtils {} @@ -300,6 +302,15 @@ impl MASQNodeUtils { Self::start_from(start.parent().unwrap()) } } + + pub fn derive_country_code_opt(node_addr: &NodeAddr) -> Option { + let country_code = get_node_location(Some(node_addr.ip_addr())); + if let Some(cc) = country_code { + Some(cc.country_code) + } else { + None + } + } } #[cfg(test)] diff --git a/multinode_integration_tests/src/masq_real_node.rs b/multinode_integration_tests/src/masq_real_node.rs index d9dc2c6da..c1d07698d 100644 --- a/multinode_integration_tests/src/masq_real_node.rs +++ b/multinode_integration_tests/src/masq_real_node.rs @@ -15,6 +15,7 @@ use masq_lib::test_utils::utils::TEST_DEFAULT_MULTINODE_CHAIN; use masq_lib::utils::{localhost, to_string}; use masq_lib::utils::{DEFAULT_CONSUMING_DERIVATION_PATH, DEFAULT_EARNING_DERIVATION_PATH}; use node_lib::blockchain::bip32::Bip32EncryptionKeyProvider; +use node_lib::neighborhood::node_location::get_node_location; use node_lib::neighborhood::DEFAULT_MIN_HOPS; use node_lib::sub_lib::accountant::{ PaymentThresholds, DEFAULT_EARNING_WALLET, DEFAULT_PAYMENT_THRESHOLDS, @@ -760,6 +761,10 @@ impl MASQNode for MASQRealNode { fn routes_data(&self) -> bool { self.guts.routes_data } + + fn country_code_opt(&self) -> Option { + MASQNodeUtils::derive_country_code_opt(&self.node_addr()) + } } impl MASQRealNode { diff --git a/multinode_integration_tests/src/utils.rs b/multinode_integration_tests/src/utils.rs index 76b555388..d0cb6e988 100644 --- a/multinode_integration_tests/src/utils.rs +++ b/multinode_integration_tests/src/utils.rs @@ -135,17 +135,12 @@ impl From<&dyn MASQNode> for AccessibleGossipRecord { accepts_connections: masq_node.accepts_connections(), routes_data: masq_node.routes_data(), version: 0, - country_code_opt: None, + country_code_opt: masq_node.country_code_opt(), }, node_addr_opt: Some(masq_node.node_addr()), signed_gossip: PlainData::new(b""), signature: CryptData::new(b""), }; - let ip_addr = masq_node.node_addr().ip_addr(); - let country_code = get_node_location(Some(ip_addr)); - if let Some(cc) = country_code { - agr.inner.country_code_opt = Some(cc.country_code) - }; agr.regenerate_signed_gossip(cryptde); agr } diff --git a/multinode_integration_tests/tests/blockchain_interaction_test.rs b/multinode_integration_tests/tests/blockchain_interaction_test.rs index 805d2a5f4..44d95dbf2 100644 --- a/multinode_integration_tests/tests/blockchain_interaction_test.rs +++ b/multinode_integration_tests/tests/blockchain_interaction_test.rs @@ -23,7 +23,7 @@ use std::time::{Duration, SystemTime}; #[test] fn debtors_are_credited_once_but_not_twice() { if is_running_under_github_actions() { - eprintln!("This test doesn't pass under GitHub Actions; don't know why, test_node_1 does not contain local descriptor in log after startup"); + eprintln!("This test doesn't pass under GitHub Actions; don't know why"); return; } let mbcs_port = find_free_port(); diff --git a/multinode_integration_tests/tests/country_code_exit_location_routing.rs b/multinode_integration_tests/tests/country_code_exit_location_routing.rs index 8f2db0374..79b043d52 100644 --- a/multinode_integration_tests/tests/country_code_exit_location_routing.rs +++ b/multinode_integration_tests/tests/country_code_exit_location_routing.rs @@ -4,7 +4,7 @@ use std::thread; use std::time::Duration; use masq_lib::messages::{ - CountryCodes, ToMessageBody, UiSetConfigurationRequest, UiSetExitLocationRequest, + CountryGroups, ToMessageBody, UiSetConfigurationRequest, UiSetExitLocationRequest, }; use masq_lib::test_utils::utils::TEST_DEFAULT_MULTINODE_CHAIN; use multinode_integration_tests_lib::masq_node::{MASQNode, PortSelector}; @@ -71,7 +71,7 @@ fn http_end_to_end_routing_test_with_exit_location() { ui.send_request( UiSetExitLocationRequest { fallback_routing: false, - exit_locations: vec![CountryCodes { + exit_locations: vec![CountryGroups { country_codes: vec!["FR".to_string()], priority: 1, }], diff --git a/node/src/bootstrapper.rs b/node/src/bootstrapper.rs index b1e920307..f49bb5707 100644 --- a/node/src/bootstrapper.rs +++ b/node/src/bootstrapper.rs @@ -506,7 +506,7 @@ impl ConfiguredByPrivilege for Bootstrapper { &alias_cryptde_null_opt, self.config.blockchain_bridge_config.chain, ); - // initialization od CountryFinder + // initialization of CountryFinder let _ = get_node_location(Some(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)))); let node_descriptor = Bootstrapper::make_local_descriptor( cryptdes.main, diff --git a/node/src/daemon/setup_reporter.rs b/node/src/daemon/setup_reporter.rs index 69970c297..1fb47be60 100644 --- a/node/src/daemon/setup_reporter.rs +++ b/node/src/daemon/setup_reporter.rs @@ -69,7 +69,7 @@ pub fn setup_cluster_from(input: Vec<(&str, &str, UiSetupResponseValueStatus)>) .collect::() } -fn daemon_shared_app() -> App<'static, 'static> { +fn setup_reporter_shared_app() -> App<'static, 'static> { shared_app(app_head()).arg(data_directory_arg(DATA_DIRECTORY_DAEMON_HELP.as_str())) } @@ -228,7 +228,7 @@ impl SetupReporterReal { } pub fn get_default_params() -> SetupCluster { - let schema = daemon_shared_app(); + let schema = setup_reporter_shared_app(); schema .p .opts @@ -498,7 +498,7 @@ impl SetupReporterReal { environment: bool, config_file: bool, ) -> Result, ConfiguratorError> { - let app = daemon_shared_app(); + let app = setup_reporter_shared_app(); let mut vcls: Vec> = vec![]; if let Some(command_line) = command_line_opt.clone() { vcls.push(Box::new(CommandLineVcl::new(command_line))); diff --git a/node/src/neighborhood/gossip.rs b/node/src/neighborhood/gossip.rs index f2b82c843..cae5007cd 100644 --- a/node/src/neighborhood/gossip.rs +++ b/node/src/neighborhood/gossip.rs @@ -5,7 +5,6 @@ use crate::neighborhood::dot_graph::{ render_dot_graph, DotRenderable, EdgeRenderable, NodeRenderable, NodeRenderableInner, }; use crate::neighborhood::neighborhood_database::NeighborhoodDatabase; -use crate::neighborhood::AccessibleGossipRecord; use crate::sub_lib::cryptde::{CryptDE, CryptData, PlainData, PublicKey}; use crate::sub_lib::hopper::MessageType; use crate::sub_lib::node_addr::NodeAddr; @@ -465,6 +464,51 @@ impl<'a> GossipBuilder<'a> { } } +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct AccessibleGossipRecord { + pub signed_gossip: PlainData, + pub signature: CryptData, + pub node_addr_opt: Option, + pub inner: NodeRecordInner_0v1, +} + +impl AccessibleGossipRecord { + pub fn regenerate_signed_gossip(&mut self, cryptde: &dyn CryptDE) { + let (signed_gossip, signature) = regenerate_signed_gossip(&self.inner, cryptde); + self.signed_gossip = signed_gossip; + self.signature = signature; + } +} + +impl TryFrom for AccessibleGossipRecord { + type Error = String; + + fn try_from(value: GossipNodeRecord) -> Result { + match serde_cbor::de::from_slice(value.signed_data.as_slice()) { + Ok(inner) => Ok(AccessibleGossipRecord { + signed_gossip: value.signed_data, + signature: value.signature, + node_addr_opt: value.node_addr_opt, + inner, + }), + Err(e) => Err(format!("{}", e)), + } + } +} + +pub fn regenerate_signed_gossip( + inner: &NodeRecordInner_0v1, + cryptde: &dyn CryptDE, // Must be the correct CryptDE for the Node from which inner came: used for signing +) -> (PlainData, CryptData) { + let signed_gossip = + PlainData::from(serde_cbor::ser::to_vec(&inner).expect("Serialization failed")); + let signature = match cryptde.sign(&signed_gossip) { + Ok(sig) => sig, + Err(e) => unimplemented!("TODO: Signing error: {:?}", e), + }; + (signed_gossip, signature) +} + #[cfg(test)] mod tests { use super::super::gossip::GossipBuilder; diff --git a/node/src/neighborhood/gossip_acceptor.rs b/node/src/neighborhood/gossip_acceptor.rs index e2570b176..02399be1b 100644 --- a/node/src/neighborhood/gossip_acceptor.rs +++ b/node/src/neighborhood/gossip_acceptor.rs @@ -1,9 +1,11 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::neighborhood::gossip::{GossipBuilder, GossipNodeRecord, Gossip_0v1}; +use crate::neighborhood::gossip::{ + AccessibleGossipRecord, GossipBuilder, GossipNodeRecord, Gossip_0v1, +}; use crate::neighborhood::neighborhood_database::{NeighborhoodDatabase, NeighborhoodDatabaseError}; use crate::neighborhood::node_record::NodeRecord; -use crate::neighborhood::{AccessibleGossipRecord, UserExitPreferences}; +use crate::neighborhood::UserExitPreferences; use crate::sub_lib::cryptde::{CryptDE, PublicKey}; use crate::sub_lib::neighborhood::{ ConnectionProgressEvent, ConnectionProgressMessage, GossipFailure_0v1, NeighborhoodMetadata, @@ -91,6 +93,23 @@ impl GossipHandler for DebutHandler { if database.node_by_key(&agrs[0].inner.public_key).is_some() { return Qualification::Unmatched; } + // TODO create optimization card: drive in the test and following commented out code, + // TODO: Imagine a brand-new network, consisting only of Node A. + // When Node B debuts, Node A cannot respond with an Introduction, + // since there's nobody to introduce. Therefore, Node A must + // respond with a single-Node Gossip that will currently be + // interpreted as a Debut by Node B, resulting in another + // single-Node Gossip from B to A. This last Gossip isn't a + // problem, because Node A will ignore it since B is already + // in its database. However, there is a possible optimization: + // drive in the code below, and Node B will no longer interpret + // Node A's acceptance Gossip as another Debut, because it will + // see that Node A already has Node B as a neighbor. This means + // Node A's original response will be interpreted as Standard + // Gossip. + // if agrs[0].inner.neighbors.contains(database.root_key()) { + // return Qualification::Unmatched; + // } match &agrs[0].node_addr_opt { None => { if agrs[0].inner.accepts_connections { @@ -356,7 +375,7 @@ impl DebutHandler { fn create_debut_gossip_response( cryptde: &dyn CryptDE, - database: &mut NeighborhoodDatabase, + database: &NeighborhoodDatabase, debut_node_key: PublicKey, ) -> Gossip_0v1 { let mut root_node = database.root().clone(); @@ -1416,6 +1435,7 @@ mod tests { use crate::test_utils::unshared_test_utils::make_cpm_recipient; use crate::test_utils::{assert_contains, main_cryptde, vec_to_set}; use actix::System; + use itertools::Itertools; use masq_lib::messages::ExitLocation; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN; @@ -1706,13 +1726,16 @@ mod tests { #[test] fn two_parallel_debuts_in_progress_handled_by_try_accept_debut_without_introduction() { - let mut root_node = make_node_record(1234, true); - let half_debuted_node = make_node_record(2345, true); + let root_node = make_node_record(1234, true); + let half_neighbor_debutant = make_node_record(2345, true); let new_debutant = make_node_record(4567, true); let root_node_cryptde = CryptDENull::from(&root_node.public_key(), TEST_DEFAULT_CHAIN); let mut dest_db = db_from_node(&root_node); - dest_db.add_node(half_debuted_node.clone()).unwrap(); - dest_db.add_arbitrary_half_neighbor(root_node.public_key(), half_debuted_node.public_key()); + dest_db.add_node(half_neighbor_debutant.clone()).unwrap(); + dest_db.add_arbitrary_half_neighbor( + root_node.public_key(), + half_neighbor_debutant.public_key(), + ); let logger = Logger::new("Debut test"); let subject = DebutHandler::new(logger); let neighborhood_metadata = make_default_neighborhood_metadata(); @@ -1735,12 +1758,8 @@ mod tests { ) => (debut_reply, dest_public_key, dest_node_addr), x => panic!("Expected Reply, got {:?}", x), }; - assert_eq!(dest_public_key, new_debutant.public_key()); assert_eq!(dest_node_addr, &new_debutant.node_addr_opt().unwrap()); - root_node - .add_half_neighbor_key(new_debutant.public_key().clone()) - .unwrap(); assert_eq!( counter_debut, GossipAcceptanceResult::Reply( @@ -2094,7 +2113,7 @@ mod tests { ); let result_introducer: &NodeRecord = - dest_db.node_by_key_mut(&agrs[0].inner.public_key).unwrap(); + dest_db.node_by_key(&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; @@ -2172,7 +2191,7 @@ mod tests { handle_result ); let result_introducer: &NodeRecord = - dest_db.node_by_key_mut(&agrs[0].inner.public_key).unwrap(); + dest_db.node_by_key(&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.resign(); @@ -2229,7 +2248,7 @@ mod tests { ); let result_introducer: &NodeRecord = - dest_db.node_by_key_mut(&agrs[0].inner.public_key).unwrap(); + dest_db.node_by_key(&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.resign(); @@ -2431,17 +2450,17 @@ mod tests { let src_root = make_node_record(1234, true); let dest_root = make_node_record(2345, true); let mut src_db = db_from_node(&src_root); - let node_a = make_node_record(3456, true); - let node_b = make_node_record(4567, true); + let node_a_fr = make_node_record(5678, true); + let node_b_us = make_node_record(4567, true); let mut dest_db = db_from_node(&dest_root); dest_db.add_node(src_root.clone()).unwrap(); dest_db.add_arbitrary_full_neighbor(dest_root.public_key(), src_root.public_key()); src_db.add_node(dest_db.root().clone()).unwrap(); - src_db.add_node(node_a.clone()).unwrap(); - src_db.add_node(node_b.clone()).unwrap(); + src_db.add_node(node_a_fr.clone()).unwrap(); + src_db.add_node(node_b_us.clone()).unwrap(); src_db.add_arbitrary_full_neighbor(src_root.public_key(), dest_root.public_key()); - src_db.add_arbitrary_half_neighbor(src_root.public_key(), &node_a.public_key()); - src_db.add_arbitrary_full_neighbor(src_root.public_key(), &node_b.public_key()); + src_db.add_arbitrary_half_neighbor(src_root.public_key(), &node_a_fr.public_key()); + src_db.add_arbitrary_full_neighbor(src_root.public_key(), &node_b_us.public_key()); src_db .node_by_key_mut(src_root.public_key()) .unwrap() @@ -2449,8 +2468,8 @@ mod tests { src_db.resign_node(src_root.public_key()); let gossip = GossipBuilder::new(&src_db) .node(src_root.public_key(), true) - .node(node_a.public_key(), false) - .node(node_b.public_key(), false) + .node(node_a_fr.public_key(), false) + .node(node_b_us.public_key(), false) .build(); let subject = StandardGossipHandler::new(Logger::new("test")); let cryptde = CryptDENull::from(dest_db.root().public_key(), TEST_DEFAULT_CHAIN); @@ -2481,7 +2500,7 @@ mod tests { assert_eq!( dest_db - .node_by_key(node_a.public_key()) + .node_by_key(node_a_fr.public_key()) .unwrap() .metadata .country_undesirability, @@ -2489,7 +2508,7 @@ mod tests { ); assert_eq!( dest_db - .node_by_key(node_b.public_key()) + .node_by_key(node_b_us.public_key()) .unwrap() .metadata .country_undesirability, @@ -2503,12 +2522,12 @@ mod tests { ); assert!(dest_db.has_full_neighbor(dest_db.root().public_key(), src_db.root().public_key())); assert_eq!( - &src_db.node_by_key(node_a.public_key()).unwrap().inner, - &dest_db.node_by_key(node_a.public_key()).unwrap().inner + &src_db.node_by_key(node_a_fr.public_key()).unwrap().inner, + &dest_db.node_by_key(node_a_fr.public_key()).unwrap().inner ); assert_eq!( - &src_db.node_by_key(node_b.public_key()).unwrap().inner, - &dest_db.node_by_key(node_b.public_key()).unwrap().inner + &src_db.node_by_key(node_b_us.public_key()).unwrap().inner, + &dest_db.node_by_key(node_b_us.public_key()).unwrap().inner ); System::current().stop(); assert_eq!(system.run(), 0); @@ -3051,10 +3070,17 @@ mod tests { fn first_debut_is_handled() { let mut root_node = make_node_record(1234, true); let root_node_cryptde = CryptDENull::from(&root_node.public_key(), TEST_DEFAULT_CHAIN); - let mut dest_db = db_from_node(&root_node); - let (gossip, mut debut_node, gossip_source) = make_debut(2345, Mode::Standard); + let mut source_db = db_from_node(&root_node); + let (gossip, mut debut_node, gossip_source) = make_debut(2345, Mode::Standard); //debut node is FR + let mut expected_source_db = db_from_node(&root_node); + expected_source_db + .add_arbitrary_half_neighbor(root_node.public_key(), debut_node.public_key()); + expected_source_db.root_mut().inner.version = 1; + expected_source_db.root_mut().resign(); + let expected_gossip_response = GossipBuilder::new(&expected_source_db) + .node(root_node.public_key(), true) + .build(); 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()], @@ -3065,59 +3091,30 @@ mod tests { }]), db_countries: vec!["CZ".to_string()], }); + let before = time_t_timestamp(); let result = subject.handle( - &mut dest_db, + &mut source_db, gossip.try_into().unwrap(), gossip_source, neighborhood_metadata, ); let after = time_t_timestamp(); - - let expected = "Reply(Gossip_0v1 { node_records: [ -GossipNodeRecord { - inner: NodeRecordInner_0v1 { - public_key: 0x01020304, - node_addr_opt: Some(1.2.3.4:[1234]), - earning_wallet: Wallet { kind: Address(0x546900db8d6e0937497133d1ae6fdf5f4b75bcd0) }, - rate_pack: RatePack { routing_byte_rate: 1235, routing_service_rate: 1434, exit_byte_rate: 1237, exit_service_rate: 1634 }, - neighbors: [0x02030405], - version: 1, - }, - node_addr_opt: Some(1.2.3.4:[1234]), - signed_data: -Length: 254 (0xfe) bytes -0000: a8 6a 70 75 62 6c 69 63 5f 6b 65 79 44 01 02 03 .jpublic_keyD... -0010: 04 6e 65 61 72 6e 69 6e 67 5f 77 61 6c 6c 65 74 .nearning_wallet -0020: a1 67 61 64 64 72 65 73 73 94 18 54 18 69 00 18 .gaddress..T.i.. -0030: db 18 8d 18 6e 09 18 37 18 49 18 71 18 33 18 d1 ....n..7.I.q.3.. -0040: 18 ae 18 6f 18 df 18 5f 18 4b 18 75 18 bc 18 d0 ...o..._.K.u.... -0050: 69 72 61 74 65 5f 70 61 63 6b a4 71 72 6f 75 74 irate_pack.qrout -0060: 69 6e 67 5f 62 79 74 65 5f 72 61 74 65 19 04 d3 ing_byte_rate... -0070: 74 72 6f 75 74 69 6e 67 5f 73 65 72 76 69 63 65 trouting_service -0080: 5f 72 61 74 65 19 05 9a 6e 65 78 69 74 5f 62 79 _rate...nexit_by -0090: 74 65 5f 72 61 74 65 19 04 d5 71 65 78 69 74 5f te_rate...qexit_ -00a0: 73 65 72 76 69 63 65 5f 72 61 74 65 19 06 62 69 service_rate..bi -00b0: 6e 65 69 67 68 62 6f 72 73 81 44 02 03 04 05 73 neighbors.D....s -00c0: 61 63 63 65 70 74 73 5f 63 6f 6e 6e 65 63 74 69 accepts_connecti -00d0: 6f 6e 73 f5 6b 72 6f 75 74 65 73 5f 64 61 74 61 ons.kroutes_data -00e0: f5 67 76 65 72 73 69 6f 6e 01 70 63 6f 75 6e 74 .gversion.pcount -00f0: 72 79 5f 63 6f 64 65 5f 6f 70 74 62 41 55 ry_code_optbAU - signature: -Length: 24 (0x18) bytes -0000: 01 02 03 04 f7 26 1e a5 d4 4a 71 d9 f8 42 08 35 .....&...Jq..B.5 -0010: e4 99 27 4a 85 7f 01 11 ..'J.... -}] }, 0x02030405, 2.3.4.5:[2345])".to_string(); - assert_eq!(format!("{:?}", result), expected); + let expected_result = GossipAcceptanceResult::Reply( + expected_gossip_response, + debut_node.public_key().clone(), + debut_node.node_addr_opt().unwrap(), + ); + assert_eq!(result, expected_result); root_node .add_half_neighbor_key(debut_node.public_key().clone()) .unwrap(); root_node.increment_version(); - root_node.metadata.last_update = dest_db.root().metadata.last_update; + root_node.metadata.last_update = source_db.root().metadata.last_update; root_node.resign(); - assert_eq!(&root_node, dest_db.root()); - let reference_node = dest_db.node_by_key_mut(debut_node.public_key()).unwrap(); + assert_eq!(&root_node, source_db.root()); + let reference_node = source_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!( @@ -3174,7 +3171,7 @@ Length: 24 (0x18) bytes root_node.metadata.last_update = dest_db.root().metadata.last_update; root_node.resign(); assert_eq!(&root_node, dest_db.root()); - let reference_node = dest_db.node_by_key_mut(debut_node.public_key()).unwrap(); + let reference_node = dest_db.node_by_key(debut_node.public_key()).unwrap(); debut_node.metadata.last_update = reference_node.metadata.last_update; debut_node.resign(); assert_node_records_eq(reference_node, &debut_node, before, after) @@ -3263,7 +3260,7 @@ Length: 24 (0x18) bytes root_node.metadata.last_update = dest_db.root().metadata.last_update; root_node.resign(); assert_eq!(&root_node, dest_db.root()); - let reference_node = dest_db.node_by_key_mut(debut_node.public_key()).unwrap(); + let reference_node = dest_db.node_by_key(debut_node.public_key()).unwrap(); debut_node.metadata.last_update = reference_node.metadata.last_update; debut_node.resign(); assert_node_records_eq(reference_node, &debut_node, before, after) @@ -3967,14 +3964,14 @@ Length: 24 (0x18) bytes &mut expected_dest_db, vec![&node_a, &node_b, &node_d, &node_e, &node_f], ); - fix_last_update_nodes(&mut expected_dest_db, &dest_db); + fix_nodes_last_updates(&mut expected_dest_db, &dest_db); expected_dest_db .node_by_key_mut(node_c.public_key()) .unwrap() .metadata .node_location_opt = None; assert_node_records_eq( - dest_db.node_by_key_mut(root_node.public_key()).unwrap(), + dest_db.node_by_key(root_node.public_key()).unwrap(), expected_dest_db .node_by_key(root_node.public_key()) .unwrap(), @@ -3982,25 +3979,25 @@ Length: 24 (0x18) bytes after, ); assert_node_records_eq( - dest_db.node_by_key_mut(node_a.public_key()).unwrap(), + dest_db.node_by_key(node_a.public_key()).unwrap(), expected_dest_db.node_by_key(node_a.public_key()).unwrap(), before, after, ); assert_node_records_eq( - dest_db.node_by_key_mut(node_b.public_key()).unwrap(), + dest_db.node_by_key(node_b.public_key()).unwrap(), expected_dest_db.node_by_key(node_b.public_key()).unwrap(), before, after, ); assert_node_records_eq( - dest_db.node_by_key_mut(node_c.public_key()).unwrap(), + dest_db.node_by_key(node_c.public_key()).unwrap(), expected_dest_db.node_by_key(node_c.public_key()).unwrap(), before, after, ); assert_node_records_eq( - dest_db.node_by_key_mut(node_d.public_key()).unwrap(), + dest_db.node_by_key(node_d.public_key()).unwrap(), expected_dest_db.node_by_key(node_d.public_key()).unwrap(), before, after, @@ -4009,7 +4006,7 @@ Length: 24 (0x18) bytes assert_eq!(dest_db.node_by_key(node_f.public_key()), None); } - fn fix_last_update_nodes( + fn fix_nodes_last_updates( expected_db: &mut NeighborhoodDatabase, dest_db: &NeighborhoodDatabase, ) { @@ -4017,7 +4014,7 @@ Length: 24 (0x18) bytes .keys() .iter() .map(|key| (*key).clone()) - .collect::>(); + .collect_vec(); keys.into_iter() .for_each(|pubkey| match dest_db.node_by_key(&pubkey) { Some(node_record) => { @@ -4118,9 +4115,9 @@ Length: 24 (0x18) bytes dest_node_mut.increment_version(); dest_node_mut.resign(); assert_eq!(result, GossipAcceptanceResult::Accepted); - fix_last_update_nodes(&mut expected_dest_db, &dest_db); + fix_nodes_last_updates(&mut expected_dest_db, &dest_db); assert_node_records_eq( - dest_db.node_by_key_mut(third_node.public_key()).unwrap(), + dest_db.node_by_key(third_node.public_key()).unwrap(), expected_dest_db .node_by_key(third_node.public_key()) .unwrap(), @@ -4128,13 +4125,13 @@ Length: 24 (0x18) bytes after, ); assert_node_records_eq( - dest_db.node_by_key_mut(src_node.public_key()).unwrap(), + dest_db.node_by_key(src_node.public_key()).unwrap(), expected_dest_db.node_by_key(src_node.public_key()).unwrap(), before, after, ); assert_node_records_eq( - dest_db.node_by_key_mut(dest_node.public_key()).unwrap(), + dest_db.node_by_key(dest_node.public_key()).unwrap(), expected_dest_db .node_by_key(dest_node.public_key()) .unwrap(), @@ -4191,7 +4188,7 @@ Length: 24 (0x18) bytes let after = time_t_timestamp(); assert_eq!(result, GossipAcceptanceResult::Ignored); assert_node_records_eq( - dest_db.node_by_key_mut(dest_root.public_key()).unwrap(), + dest_db.node_by_key(dest_root.public_key()).unwrap(), original_dest_db .node_by_key(dest_root.public_key()) .unwrap(), @@ -4199,13 +4196,13 @@ Length: 24 (0x18) bytes after, ); assert_node_records_eq( - dest_db.node_by_key_mut(src_root.public_key()).unwrap(), + dest_db.node_by_key(src_root.public_key()).unwrap(), original_dest_db.node_by_key(src_root.public_key()).unwrap(), before, after, ); assert_node_records_eq( - dest_db.node_by_key_mut(current_node.public_key()).unwrap(), + dest_db.node_by_key(current_node.public_key()).unwrap(), original_dest_db .node_by_key(current_node.public_key()) .unwrap(), @@ -4213,7 +4210,7 @@ Length: 24 (0x18) bytes after, ); assert_node_records_eq( - dest_db.node_by_key_mut(obsolete_node.public_key()).unwrap(), + dest_db.node_by_key(obsolete_node.public_key()).unwrap(), original_dest_db .node_by_key(obsolete_node.public_key()) .unwrap(), @@ -4546,12 +4543,7 @@ Length: 24 (0x18) bytes GossipAcceptorReal::new(crypt_de) } - fn assert_node_records_eq( - actual: &mut NodeRecord, - expected: &NodeRecord, - before: u32, - after: u32, - ) { + fn assert_node_records_eq(actual: &NodeRecord, expected: &NodeRecord, before: u32, after: u32) { assert!( actual.metadata.last_update >= before, "Timestamp should have been at least {}, but was {}", diff --git a/node/src/neighborhood/gossip_producer.rs b/node/src/neighborhood/gossip_producer.rs index 4f0ef44a9..85d9cdc06 100644 --- a/node/src/neighborhood/gossip_producer.rs +++ b/node/src/neighborhood/gossip_producer.rs @@ -102,9 +102,9 @@ impl GossipProducerReal { mod tests { use super::super::gossip::GossipNodeRecord; use super::*; + use crate::neighborhood::gossip::AccessibleGossipRecord; use crate::neighborhood::neighborhood_database::ISOLATED_NODE_GRACE_PERIOD_SECS; use crate::neighborhood::node_record::{NodeRecord, NodeRecordInner_0v1}; - use crate::neighborhood::AccessibleGossipRecord; use crate::sub_lib::cryptde::CryptDE; use crate::sub_lib::cryptde_null::CryptDENull; use crate::sub_lib::utils::time_t_timestamp; diff --git a/node/src/neighborhood/mod.rs b/node/src/neighborhood/mod.rs index ffe3e3215..c302ed400 100644 --- a/node/src/neighborhood/mod.rs +++ b/node/src/neighborhood/mod.rs @@ -15,16 +15,15 @@ use crate::database::db_initializer::{DbInitializer, DbInitializerReal}; use crate::db_config::persistent_configuration::{ PersistentConfigError, PersistentConfiguration, PersistentConfigurationReal, }; -use crate::neighborhood::gossip::{DotGossipEndpoint, GossipNodeRecord, Gossip_0v1}; +use crate::neighborhood::gossip::{AccessibleGossipRecord, DotGossipEndpoint, Gossip_0v1}; use crate::neighborhood::gossip_acceptor::GossipAcceptanceResult; use crate::neighborhood::node_location::get_node_location; -use crate::neighborhood::node_record::NodeRecordInner_0v1; use crate::neighborhood::overall_connection_status::{ OverallConnectionStage, OverallConnectionStatus, }; use crate::stream_messages::RemovedStreamType; +use crate::sub_lib::cryptde::CryptDE; use crate::sub_lib::cryptde::PublicKey; -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}; @@ -61,12 +60,15 @@ use gossip_producer::GossipProducer; use gossip_producer::GossipProducerReal; use itertools::Itertools; use masq_lib::blockchains::chains::Chain; -use masq_lib::constants::EXIT_COUNTRY_MISSING_COUNTRIES_ERROR; +use masq_lib::constants::{ + DEFAULT_PREALLOCATION_VEC, EXIT_COUNTRY_MISSING_COUNTRIES_ERROR, PAYLOAD_ZERO_SIZE, +}; use masq_lib::crash_point::CrashPoint; +use masq_lib::exit_locations::ExitLocationSet; use masq_lib::logger::Logger; use masq_lib::messages::{ - ExitLocation, ExitLocationSet, FromMessageBody, ToMessageBody, UiConnectionStage, - UiConnectionStatusRequest, UiSetExitLocationRequest, UiSetExitLocationResponse, + ExitLocation, FromMessageBody, ToMessageBody, UiConnectionStage, UiConnectionStatusRequest, + UiSetExitLocationRequest, UiSetExitLocationResponse, }; use masq_lib::messages::{UiConnectionStatusResponse, UiShutdownRequest}; use masq_lib::ui_gateway::MessagePath::Conversation; @@ -74,7 +76,7 @@ use masq_lib::ui_gateway::{MessageBody, MessageTarget, NodeFromUiMessage, NodeTo use masq_lib::utils::{exit_process, ExpectValue, NeighborhoodModeLight}; use neighborhood_database::NeighborhoodDatabase; use node_record::NodeRecord; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use std::convert::TryFrom; use std::fmt::Debug; use std::net::{IpAddr, SocketAddr}; @@ -85,74 +87,11 @@ 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 ZERO_UNDESIRABILITY: u32 = 0; 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<'a> { - routes: Vec<(Vec<&'a PublicKey>, i64)>, -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum FallbackPreference { - 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 - fallback_preference: FallbackPreference, - locations_opt: Option>, - db_countries: Vec, -} - -impl UserExitPreferences { - fn new() -> UserExitPreferences { - UserExitPreferences { - exit_countries: vec![], - fallback_preference: FallbackPreference::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.fallback_preference == FallbackPreference::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, hopper_opt: Option>, @@ -434,38 +373,6 @@ impl Handler for Neighborhood { } } -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct AccessibleGossipRecord { - pub signed_gossip: PlainData, - pub signature: CryptData, - pub node_addr_opt: Option, - pub inner: NodeRecordInner_0v1, -} - -impl AccessibleGossipRecord { - pub fn regenerate_signed_gossip(&mut self, cryptde: &dyn CryptDE) { - let (signed_gossip, signature) = regenerate_signed_gossip(&self.inner, cryptde); - self.signed_gossip = signed_gossip; - self.signature = signature; - } -} - -impl TryFrom for AccessibleGossipRecord { - type Error = String; - - fn try_from(value: GossipNodeRecord) -> Result { - match serde_cbor::de::from_slice(value.signed_data.as_slice()) { - Ok(inner) => Ok(AccessibleGossipRecord { - signed_gossip: value.signed_data, - signature: value.signature, - node_addr_opt: value.node_addr_opt, - inner, - }), - Err(e) => Err(format!("{}", e)), - } - } -} - #[derive(Debug, PartialEq, Eq, Clone, Copy)] enum RouteDirection { Over, @@ -582,12 +489,9 @@ impl Neighborhood { fn handle_new_ip_location(&mut self, new_public_ip: IpAddr) { let node_location_opt = get_node_location(Some(new_public_ip)); - self.neighborhood_database - .root_mut() - .metadata - .node_location_opt = node_location_opt.clone(); - self.neighborhood_database.root_mut().inner.country_code_opt = - node_location_opt.map(|nl| nl.country_code); + let root_node = self.neighborhood_database.root_mut(); + root_node.metadata.node_location_opt = node_location_opt.clone(); + root_node.inner.country_code_opt = node_location_opt.map(|nl| nl.country_code); } fn handle_route_query_message(&mut self, msg: RouteQueryMessage) -> Option { @@ -652,6 +556,15 @@ impl Neighborhood { &self.logger, ); } + self.user_exit_preferences.db_countries = self.init_db_countries(); + if let Some(exit_locations_by_priority) = + self.user_exit_preferences.locations_opt.clone() + { + for exit_location in &exit_locations_by_priority { + self.enrich_exit_countries(&exit_location.country_codes); + } + self.set_country_undesirability_and_exit_countries(&exit_locations_by_priority); + } self.search_for_a_new_route(); } ConfigChange::UpdatePassword(new_password) => { @@ -891,8 +804,8 @@ impl Neighborhood { self.handle_gossip_ignored(ignored_node_name, gossip_record_count) } GossipAcceptanceResult::Ban(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 + // TODO in case we introduce Ban machinery we want to reinitialize the db_countries here as well + // in that case, we need to make new process in init_db_countries to exclude banned node, from the result 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); } @@ -1284,12 +1197,13 @@ impl Neighborhood { } } - fn validate_fallback_country_exit_codes(&self, last_node: &PublicKey) -> bool { + fn validate_country_code_when_fallback_routing(&self, last_node: &PublicKey) -> bool { let last_cc = match self.neighborhood_database.node_by_key(last_node) { - Some(nr) => match nr.clone().inner.country_code_opt { - Some(cc) => cc, - None => "ZZ".to_string(), - }, + Some(nr) => nr + .inner + .country_code_opt + .clone() + .unwrap_or_else(|| "ZZ".to_string()), None => "ZZ".to_string(), }; if self.user_exit_preferences.exit_countries.contains(&last_cc) { @@ -1299,9 +1213,6 @@ impl Neighborhood { 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; } @@ -1311,65 +1222,83 @@ impl Neighborhood { fn validate_last_node_country_code( &self, - first_node_key: &PublicKey, + last_node_key: &PublicKey, research_neighborhood: bool, direction: RouteDirection, ) -> bool { - if self.user_exit_preferences.fallback_preference == FallbackPreference::Nothing - || (self.user_exit_preferences.fallback_preference - == FallbackPreference::ExitCountryWithFallback - && self.validate_fallback_country_exit_codes(first_node_key)) - || research_neighborhood - || direction == RouteDirection::Back - { + if self.is_always_true(last_node_key, research_neighborhood, direction) { true // Zero- and single-hop routes are not subject to exit-too-close restrictions } else { - match self.neighborhood_database.node_by_key(first_node_key) { - Some(node_record) => match &node_record.inner.country_code_opt { - Some(country_code) => self + if let Some(node_record) = self.neighborhood_database.node_by_key(last_node_key) { + if let Some(country_code) = &node_record.inner.country_code_opt { + return self .user_exit_preferences .exit_countries - .contains(country_code), - _ => false, - }, - _ => false, + .contains(country_code); + } } + false } } + fn is_always_true( + &self, + last_node_key: &PublicKey, + research_neighborhood: bool, + direction: RouteDirection, + ) -> bool { + self.user_exit_preferences.fallback_preference == FallbackPreference::Nothing + || (self.user_exit_preferences.fallback_preference + == FallbackPreference::ExitCountryWithFallback + && self.validate_country_code_when_fallback_routing(last_node_key)) + || research_neighborhood + || direction == RouteDirection::Back + } + fn compute_undesirability( node_record: &NodeRecord, payload_size: u64, undesirability_type: UndesirabilityType, logger: &Logger, ) -> i64 { - let mut rate_undesirability = match undesirability_type { - 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 + match undesirability_type { + UndesirabilityType::Relay => { + node_record.inner.rate_pack.routing_charge(payload_size) as i64 } - UndesirabilityType::ExitAndRouteResponse => { - node_record.inner.rate_pack.exit_charge(payload_size) - + node_record.inner.rate_pack.routing_charge(payload_size) + UndesirabilityType::ExitRequest(None) => { + node_record.inner.rate_pack.exit_charge(payload_size) as i64 + + node_record.metadata.country_undesirability as i64 } - } as i64; - if let UndesirabilityType::ExitRequest(Some(hostname)) = undesirability_type { - if node_record.metadata.unreachable_hosts.contains(hostname) { - trace!( - logger, - "Node with PubKey {:?} failed to reach host {:?} during ExitRequest; Undesirability: {} + {} = {}", - node_record.public_key(), - hostname, - rate_undesirability, - UNREACHABLE_HOST_PENALTY, - rate_undesirability + UNREACHABLE_HOST_PENALTY - ); - rate_undesirability += UNREACHABLE_HOST_PENALTY; + UndesirabilityType::ExitRequest(Some(hostname)) => { + let exit_undesirability = + node_record.inner.rate_pack.exit_charge(payload_size) as i64; + let country_undesirability = node_record.metadata.country_undesirability as i64; + let unreachable_undesirability = if node_record + .metadata + .unreachable_hosts + .contains(hostname) + { + trace!( + logger, + "Node with PubKey {:?} failed to reach host {:?} during ExitRequest; Undesirability: {} + {} + {} = {}", + node_record.public_key(), + hostname, + exit_undesirability, + UNREACHABLE_HOST_PENALTY, + country_undesirability, + exit_undesirability + UNREACHABLE_HOST_PENALTY + country_undesirability + ); + UNREACHABLE_HOST_PENALTY + } else { + 0i64 + }; + exit_undesirability + unreachable_undesirability + country_undesirability + } + UndesirabilityType::ExitAndRouteResponse => { + node_record.inner.rate_pack.exit_charge(payload_size) as i64 + + node_record.inner.rate_pack.routing_charge(payload_size) as i64 } } - - rate_undesirability } fn is_orig_node_on_back_leg( @@ -1392,44 +1321,28 @@ impl Neighborhood { return_route_id } - pub fn find_exit_location<'a>( + pub fn find_exit_locations<'a>( &'a self, source: &'a PublicKey, minimum_hops: usize, - payload_size: usize, ) -> Vec<&'a PublicKey> { let mut minimum_undesirability = i64::MAX; let initial_undesirability = 0; let research_exits: &mut Vec<&'a PublicKey> = &mut vec![]; - let mut prefix = Vec::with_capacity(10); + let mut prefix = Vec::with_capacity(DEFAULT_PREALLOCATION_VEC); prefix.push(source); - let over_routes = self.routing_engine( - &mut prefix, + let _ = self.routing_engine( + prefix, initial_undesirability, None, minimum_hops, - payload_size, + PAYLOAD_ZERO_SIZE, RouteDirection::Over, &mut minimum_undesirability, None, true, research_exits, ); - let mut result_exit: HashMap = HashMap::new(); - 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)], - }); - } - }); research_exits.to_vec() } @@ -1453,11 +1366,12 @@ impl Neighborhood { let mut minimum_undesirability = i64::MAX; let initial_undesirability = self.compute_initial_undesirability(source, payload_size as u64, direction); - let mut prefix = Vec::with_capacity(10); + let mut prefix = Vec::with_capacity(DEFAULT_PREALLOCATION_VEC); + //TODO we can have an investigation, if this DEFAULT_PREALLOCATION_VEC is not too much, same in find_exit_locations prefix.push(source); let result = self .routing_engine( - &mut vec![source], + vec![source], initial_undesirability, target_opt, minimum_hops, @@ -1481,7 +1395,7 @@ impl Neighborhood { #[allow(clippy::too_many_arguments)] fn routing_engine<'a>( &'a self, - prefix: &mut Vec<&'a PublicKey>, + prefix: Vec<&'a PublicKey>, undesirability: i64, target_opt: Option<&'a PublicKey>, hops_remaining: usize, @@ -1495,7 +1409,6 @@ impl Neighborhood { if undesirability > *minimum_undesirability && !research_neighborhood { return vec![]; } - //TODO when target node is present ignore all country_codes selection - write test for route back and ignore the country code let first_node_key = prefix.first().expect("Empty prefix"); let previous_node = self .neighborhood_database @@ -1545,7 +1458,7 @@ impl Neighborhood { && (self.user_exit_preferences.fallback_preference == FallbackPreference::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: + // in case we do not investigate neighborhood for country codes, or we are not looking for particular country exit: // don't continue a targetless search past the minimum hop count vec![] } else { @@ -1568,7 +1481,7 @@ impl Neighborhood { #[allow(clippy::too_many_arguments)] fn routing_guts<'a>( &'a self, - prefix: &mut [&'a PublicKey], + prefix: Vec<&'a PublicKey>, undesirability: i64, target_opt: Option<&'a PublicKey>, hops_remaining: usize, @@ -1577,7 +1490,7 @@ impl Neighborhood { minimum_undesirability: &mut i64, hostname_opt: Option<&str>, research_neighborhood: bool, - research_exits: &mut Vec<&'a PublicKey>, + exits_research: &mut Vec<&'a PublicKey>, previous_node: &NodeRecord, ) -> Vec { // Go through all the neighbors and compute shorter routes through all the ones we're not already using. @@ -1590,7 +1503,7 @@ impl Neighborhood { || Self::is_orig_node_on_back_leg(**node_record, target_opt, direction) }) .flat_map(|node_record| { - let mut new_prefix = prefix.to_owned(); + let mut new_prefix = prefix.clone(); new_prefix.push(node_record.public_key()); let new_hops_remaining = if hops_remaining == 0 { @@ -1610,7 +1523,7 @@ impl Neighborhood { ); self.routing_engine( - &mut new_prefix, + new_prefix, new_undesirability, target_opt, new_hops_remaining, @@ -1619,7 +1532,7 @@ impl Neighborhood { minimum_undesirability, hostname_opt, research_neighborhood, - research_exits, + exits_research, ) }) .collect() @@ -1699,22 +1612,23 @@ impl Neighborhood { client_id: u64, context_id: u64, ) { - let (exit_locations_by_priority, missing_locations) = + //TODO write test that contains more CountryGroups than countries in neighborhood db to check if unexistent country codes in db are filtered out from ExitLocation + let (exit_locations_by_priority, missing_countries) = self.extract_exit_locations_from_message(&message); self.user_exit_preferences.fallback_preference = match ( message.fallback_routing, exit_locations_by_priority.is_empty(), ) { - (true, true) => FallbackPreference::Nothing, + (true, true) | (false, true) => FallbackPreference::Nothing, (true, false) => FallbackPreference::ExitCountryWithFallback, (false, false) => FallbackPreference::ExitCountryNoFallback, - (false, true) => FallbackPreference::Nothing, }; let fallback_status = match self.user_exit_preferences.fallback_preference { - FallbackPreference::Nothing => "Fallback Routing is set.", - FallbackPreference::ExitCountryWithFallback => "Fallback Routing is set.", + FallbackPreference::Nothing | FallbackPreference::ExitCountryWithFallback => { + "Fallback Routing is set." + } FallbackPreference::ExitCountryNoFallback => "Fallback Routing NOT set.", }; @@ -1724,23 +1638,11 @@ impl Neighborhood { 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 + self.exit_location_logger_output( + exit_locations_by_priority, + &missing_countries, + fallback_status, ); - if !missing_locations.is_empty() { - warning!( - self.logger, - "Exit Location: following desired countries are missing in Neighborhood {:?}", &missing_locations - ); - } } false => info!( self.logger, @@ -1750,7 +1652,7 @@ impl Neighborhood { let message = self.create_exit_location_response( client_id, context_id, - missing_locations, + missing_countries, message.show_countries, ); self.node_to_ui_recipient_opt @@ -1760,71 +1662,109 @@ impl Neighborhood { .expect("UiGateway is dead"); } + fn exit_location_logger_output( + &mut self, + exit_locations_by_priority: Vec, + missing_locations: &Vec, + fallback_status: &str, + ) { + self.logger.info(|| { + 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.", + }; + format!( + "{} {}{}", + 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 + ); + } + } + fn error_message_indicates(&self, missing_countries: &mut Vec) -> bool { let mut desired_countries: Vec = vec![]; if let Some(exit_vec) = self.user_exit_preferences.locations_opt.as_ref() { for location in exit_vec { - let mut to_appedn = location.country_codes.clone(); - desired_countries.append(&mut to_appedn) + let mut to_append = location.country_codes.clone(); + desired_countries.append(&mut to_append) } } - if desired_countries.is_empty() && missing_countries.is_empty() { return false; } - missing_countries.sort(); desired_countries.sort(); - let comp_missing_countries = missing_countries.clone(); - let comp_exit_countries = desired_countries; - comp_missing_countries.eq(&comp_exit_countries) + missing_countries.sort(); + missing_countries == &desired_countries } fn create_exit_location_response( &self, client_id: u64, context_id: u64, - missing_countries: Vec, + mut missing_countries: Vec, show_countries_flag: bool, ) -> NodeToUiMessage { - let fallback_routing = match &self.user_exit_preferences.fallback_preference { - FallbackPreference::Nothing => true, - FallbackPreference::ExitCountryWithFallback => true, - FallbackPreference::ExitCountryNoFallback => false, - }; - let exit_locations = self - .user_exit_preferences - .locations_opt - .clone() - .unwrap_or_default(); - let show_countries = match show_countries_flag { - true => Some(self.user_exit_preferences.db_countries.clone()), - false => None, - }; - - if !self.error_message_indicates(&mut missing_countries.clone()) { + let fallback_routing = self.is_fallback_routing_active(); + let exit_locations = self.get_locations_opt(); + let countries_to_show = self.get_countries_to_show(show_countries_flag); + let missing_countries_message: String = missing_countries.join(", "); + if self.error_message_indicates(&mut missing_countries) { + NodeToUiMessage { + target: MessageTarget::ClientId(client_id), + body: MessageBody { + opcode: "exitLocation".to_string(), + path: Conversation(context_id), + payload: Err(( + EXIT_COUNTRY_MISSING_COUNTRIES_ERROR, + missing_countries_message, + )), + }, + } + } else { NodeToUiMessage { target: MessageTarget::ClientId(client_id), body: UiSetExitLocationResponse { fallback_routing, - exit_locations, - exit_countries: show_countries, + exit_country_selection: exit_locations, + exit_countries: countries_to_show, missing_countries, } .tmb(context_id), } - } else { - let missing_message: String = missing_countries.join(", "); - NodeToUiMessage { - target: MessageTarget::ClientId(client_id), - body: MessageBody { - opcode: "exitLocation".to_string(), - path: Conversation(context_id), - payload: Err((EXIT_COUNTRY_MISSING_COUNTRIES_ERROR, missing_message)), - }, - } } } + fn get_countries_to_show(&self, show_countries_flag: bool) -> Option> { + match show_countries_flag { + true => Some(self.user_exit_preferences.db_countries.clone()), + false => None, + } + } + + fn is_fallback_routing_active(&self) -> bool { + match &self.user_exit_preferences.fallback_preference { + FallbackPreference::Nothing => true, + FallbackPreference::ExitCountryWithFallback => true, + FallbackPreference::ExitCountryNoFallback => false, + } + } + + fn get_locations_opt(&self) -> Vec { + self.user_exit_preferences + .locations_opt + .clone() + .unwrap_or_default() + } + 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() { @@ -1851,7 +1791,7 @@ impl Neighborhood { false => { self.user_exit_preferences.exit_countries = vec![]; for node_record in nodes { - node_record.metadata.country_undesirability = 0u32; + node_record.metadata.country_undesirability = ZERO_UNDESIRABILITY; } } } @@ -1861,46 +1801,68 @@ impl Neighborhood { &mut self, message: &UiSetExitLocationRequest, ) -> (Vec, Vec) { - //TODO perform following update of db_countries only in Gossip_Acceptor self.user_exit_preferences.db_countries = self.init_db_countries(); - let mut countries_lack_in_neighborhood = vec![]; + let mut countries_not_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.fallback_preference - == FallbackPreference::ExitCountryWithFallback - { - self.user_exit_preferences.exit_countries.push(code.clone()); - if self.user_exit_preferences.fallback_preference - == FallbackPreference::ExitCountryWithFallback - { - countries_lack_in_neighborhood.push(code.clone()); - } - } else { - countries_lack_in_neighborhood.push(code.clone()); - } - } + let requested_country_codes = &cc.country_codes; + countries_not_in_neighborhood + .extend(self.enrich_exit_countries(requested_country_codes)); ExitLocation { country_codes: cc.country_codes, priority: cc.priority, } }) .collect(), - countries_lack_in_neighborhood, + countries_not_in_neighborhood, ) } + fn enrich_exit_countries(&mut self, country_codes: &Vec) -> Vec { + let mut countries_not_in_neighborhood = vec![]; + for code in country_codes { + if self.code_in_db_countries_or_fallback_active(code) { + if !self.user_exit_preferences.exit_countries.contains(code) { + self.user_exit_preferences.exit_countries.push(code.clone()); + } + if self.fallback_active_and_code_missing_in_db_countries(code) { + countries_not_in_neighborhood.push(code.clone()); + } + } else { + if let Some(index) = self + .user_exit_preferences + .exit_countries + .iter() + .position(|item| item.eq(code)) + { + self.user_exit_preferences.exit_countries.remove(index); + } + countries_not_in_neighborhood.push(code.clone()); + } + } + countries_not_in_neighborhood + } + + fn fallback_active_and_code_missing_in_db_countries(&mut self, code: &String) -> bool { + (self.user_exit_preferences.fallback_preference + == FallbackPreference::ExitCountryWithFallback) + && !self.user_exit_preferences.db_countries.contains(code) + } + + fn code_in_db_countries_or_fallback_active(&mut self, code: &String) -> bool { + self.user_exit_preferences.db_countries.contains(code) + || (self.user_exit_preferences.fallback_preference + == FallbackPreference::ExitCountryWithFallback) + } + 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_nodes = self - .find_exit_location(root_key, min_hops, 0usize) - .to_owned(); + let exit_nodes = self.find_exit_locations(root_key, min_hops).to_owned(); let mut db_countries = vec![]; if !exit_nodes.is_empty() { for pub_key in exit_nodes { @@ -2087,17 +2049,75 @@ impl Neighborhood { } } -pub fn regenerate_signed_gossip( - inner: &NodeRecordInner_0v1, - cryptde: &dyn CryptDE, // Must be the correct CryptDE for the Node from which inner came: used for signing -) -> (PlainData, CryptData) { - let signed_gossip = - PlainData::from(serde_cbor::ser::to_vec(&inner).expect("Serialization failed")); - let signature = match cryptde.sign(&signed_gossip) { - Ok(sig) => sig, - Err(e) => unimplemented!("TODO: Signing error: {:?}", e), - }; - (signed_gossip, signature) +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct ExitLocationsRoutes<'a> { + routes: Vec<(Vec<&'a PublicKey>, i64)>, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum FallbackPreference { + Nothing, + ExitCountryWithFallback, + ExitCountryNoFallback, +} + +//TODO create big comment about all members and its utilization +#[derive(Clone, Debug)] +pub struct UserExitPreferences { + exit_countries: Vec, //if we cross number of country_codes used in one workflow over 34, we want to change this member to HashSet + fallback_preference: FallbackPreference, + locations_opt: Option>, //TODO remove Option from NeighborhoodMetadata and create there TODO to optimize it in future via reference + db_countries: Vec, +} + +impl UserExitPreferences { + fn new() -> UserExitPreferences { + UserExitPreferences { + exit_countries: vec![], + fallback_preference: FallbackPreference::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 Self::should_set_country_undesirability(&country_code, exit_location) { + node_record.metadata.country_undesirability = + Self::calculate_country_undesirability(exit_location.priority as u32); + } + if self.is_unreachable_country_penalty(&country_code) { + node_record.metadata.country_undesirability = UNREACHABLE_COUNTRY_PENALTY; + } + } + } + None => (), + } + } + + fn should_set_country_undesirability( + country_code: &String, + exit_location: &ExitLocation, + ) -> bool { + exit_location.country_codes.contains(country_code) && country_code != ZZ_COUNTRY_CODE_STRING + } + + fn is_unreachable_country_penalty(&self, country_code: &String) -> bool { + (self.fallback_preference == FallbackPreference::ExitCountryWithFallback + && !self.exit_countries.contains(country_code)) + || country_code == ZZ_COUNTRY_CODE_STRING + } + + fn calculate_country_undesirability(priority: u32) -> u32 { + COUNTRY_UNDESIRABILITY_FACTOR * (priority - 1u32) + } } #[derive(PartialEq, Eq, Debug)] @@ -2130,6 +2150,7 @@ mod tests { use serde_cbor; use std::any::TypeId; use std::cell::RefCell; + use std::collections::HashMap; use std::convert::TryInto; use std::net::{IpAddr, SocketAddr}; use std::path::Path; @@ -2142,20 +2163,20 @@ mod tests { use masq_lib::constants::{DEFAULT_CHAIN, TLS_PORT}; use masq_lib::messages::{ - CountryCodes, ToMessageBody, UiConnectionChangeBroadcast, UiConnectionStage, + CountryGroups, 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; use masq_lib::ui_gateway::MessageTarget; + use masq_lib::ui_gateway::{MessageBody, MessagePath}; use masq_lib::utils::running_test; use crate::db_config::persistent_configuration::PersistentConfigError; - use crate::neighborhood::gossip::GossipBuilder; use crate::neighborhood::gossip::Gossip_0v1; + use crate::neighborhood::gossip::{GossipBuilder, GossipNodeRecord}; use crate::neighborhood::node_record::{NodeRecordInner_0v1, NodeRecordInputs}; use crate::stream_messages::{NonClandestineAttributes, RemovedStreamType}; - use crate::sub_lib::cryptde::{decodex, encodex, CryptData}; + use crate::sub_lib::cryptde::{decodex, encodex, CryptData, PlainData}; use crate::sub_lib::cryptde_null::CryptDENull; use crate::sub_lib::dispatcher::Endpoint; use crate::sub_lib::hop::LiveHop; @@ -2203,6 +2224,16 @@ mod tests { use crate::test_utils::unshared_test_utils::notify_handlers::NotifyLaterHandleMock; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + impl Neighborhood { + fn get_node_country_undesirability(&self, pubkey: &PublicKey) -> u32 { + self.neighborhood_database + .node_by_key(pubkey) + .unwrap() + .metadata + .country_undesirability + } + } + impl NeighborhoodDatabase { pub fn set_root_key(&mut self, key: &PublicKey) { self.this_node = key.clone(); @@ -2253,40 +2284,73 @@ mod tests { } #[test] - fn test_wtih_standard_gossip_have_new_exit_node_in_database() { + fn init_db_countries_works_properly() { let mut subject = make_standard_subject(); - let root_node_key = subject.neighborhood_database.root_key().clone(); - let root_node = subject.neighborhood_database.root().clone(); - let first_neighbor = make_node_record(1111, true); - let second_neighbor = make_node_record(2222, true); - + subject.min_hops = Hops::OneHop; + let root_node_key = subject.neighborhood_database.root().public_key().clone(); + let mut first_neighbor = make_node_record(1111, true); + first_neighbor.inner.country_code_opt = Some("CZ".to_string()); + let mut second_neighbor = make_node_record(2222, true); + second_neighbor.inner.country_code_opt = Some("DE".to_string()); subject .neighborhood_database .add_node(first_neighbor.clone()) .unwrap(); + subject + .neighborhood_database + .add_node(second_neighbor.clone()) + .unwrap(); subject .neighborhood_database .add_arbitrary_full_neighbor(&root_node_key, first_neighbor.public_key()); - let mut gossip_db = subject.neighborhood_database.clone(); - - gossip_db.set_root_key(first_neighbor.public_key()); - gossip_db.remove_node(&root_node_key); - gossip_db.add_node(root_node).unwrap(); - gossip_db.add_arbitrary_full_neighbor(first_neighbor.public_key(), &root_node_key); - gossip_db.add_node(second_neighbor.clone()).unwrap(); - gossip_db - .add_arbitrary_full_neighbor(first_neighbor.public_key(), second_neighbor.public_key()); - gossip_db.root_mut().inner.version = 1; - let resigner = gossip_db - .node_by_key_mut(first_neighbor.public_key()) + subject + .neighborhood_database + .add_arbitrary_full_neighbor(&root_node_key, second_neighbor.public_key()); + let filled_db_countries = subject.init_db_countries(); + + subject + .neighborhood_database + .remove_arbitrary_half_neighbor(&root_node_key, first_neighbor.public_key()); + subject + .neighborhood_database + .remove_arbitrary_half_neighbor(&root_node_key, second_neighbor.public_key()); + let emptied_db_countries = subject.init_db_countries(); + + assert_eq!(filled_db_countries, &["CZ".to_string(), "DE".to_string()]); + assert!(emptied_db_countries.is_empty()); + } + + #[test] + fn standard_gossip_results_in_exit_node_in_database() { + let mut subject = make_standard_subject(); + let root_node_key = subject.neighborhood_database.root_key().clone(); + let source_node = make_node_record(1111, true); //US + let first_node = make_node_record(2222, true); //FR + let second_node = make_node_record(3333, false); + subject + .neighborhood_database + .add_node(source_node.clone()) + .unwrap(); + subject + .neighborhood_database + .add_node(second_node.clone()) .unwrap(); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(&root_node_key, source_node.public_key()); + let mut source_db = subject.neighborhood_database.clone(); + source_db.set_root_key(source_node.public_key()); + source_db.add_arbitrary_full_neighbor(source_node.public_key(), &root_node_key); + source_db.add_node(first_node.clone()).unwrap(); + source_db.add_arbitrary_full_neighbor(source_node.public_key(), first_node.public_key()); + source_db.root_mut().inner.version = 1; + let resigner = source_db.node_by_key_mut(source_node.public_key()).unwrap(); resigner.resign(); - - let standard_gossip = GossipBuilder::new(&gossip_db) - .node(first_neighbor.public_key(), true) - .node(second_neighbor.public_key(), false) + let standard_gossip = GossipBuilder::new(&source_db) + .node(source_node.public_key(), true) + .node(second_node.public_key(), false) + .node(first_node.public_key(), false) .build(); - let peer_actors = peer_actors_builder().build(); subject.handle_bind_message(BindMessage { peer_actors }); subject.min_hops = Hops::OneHop; @@ -2306,26 +2370,29 @@ mod tests { } #[test] - fn test_with_introduction_always_have_half_neighborship_in_handle_gossip() { - let mut debut_subject = make_debut_subject(); + fn introduction_results_in_full_neighborship_in_debutant_db_and_enrich_db_countries_on_one_hop() + { + let debut_node = make_global_cryptde_node_record(1111, true); + let mut debut_subject = neighborhood_from_nodes(&debut_node, None); + debut_subject.min_hops = Hops::OneHop; + let persistent_config = + PersistentConfigurationMock::new().set_past_neighbors_result(Ok(())); + debut_subject.persistent_config_opt = Some(Box::new(persistent_config)); let debut_root_key = debut_subject.neighborhood_database.root_key().clone(); - let introducer_node = make_node_record(3333, true); - let introducee = make_node_record(2222, true); + let introducer_node = make_node_record(3333, true); //AU + let introducee = make_node_record(2222, true); //FR let introducer_root_key = introducer_node.public_key().clone(); let mut introducer_db = debut_subject.neighborhood_database.clone(); - introducer_db.set_root_key(&introducer_root_key); - introducer_db.add_node(introducer_node).unwrap(); + introducer_db.add_node(introducer_node.clone()).unwrap(); introducer_db.add_arbitrary_half_neighbor(&introducer_root_key, &debut_root_key); introducer_db.add_node(introducee.clone()).unwrap(); introducer_db.add_arbitrary_full_neighbor(&introducer_root_key, introducee.public_key()); - let introduction_gossip = GossipBuilder::new(&introducer_db) .node(&introducer_root_key, true) .node(introducee.public_key(), true) .build(); let peer_actors = peer_actors_builder().build(); - debut_subject.min_hops = Hops::OneHop; let exit_nodes_before_gossip = debut_subject.init_db_countries(); debut_subject.handle_bind_message(BindMessage { peer_actors }); @@ -2342,32 +2409,6 @@ mod tests { ); } - #[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" @@ -3567,6 +3608,88 @@ mod tests { assert_eq!(juicy_parts(result_1), (1, 1)); } + #[test] + fn min_hops_change_affects_db_countries_and_exit_location_settings() { + let mut subject = make_standard_subject(); + let root_node_ch = subject.neighborhood_database.root().clone(); + let neighbor_one_au = make_node_record(1234, true); + let neighbor_two_fr = make_node_record(2345, true); + let neighbor_three_cn = make_node_record(3456, true); + let neighbor_four_us = make_node_record(4567, true); + subject + .neighborhood_database + .add_node(neighbor_one_au.clone()) + .unwrap(); + subject + .neighborhood_database + .add_node(neighbor_two_fr.clone()) + .unwrap(); + subject + .neighborhood_database + .add_node(neighbor_three_cn.clone()) + .unwrap(); + subject + .neighborhood_database + .add_node(neighbor_four_us.clone()) + .unwrap(); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(root_node_ch.public_key(), neighbor_one_au.public_key()); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(neighbor_one_au.public_key(), neighbor_two_fr.public_key()); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(neighbor_two_fr.public_key(), neighbor_three_cn.public_key()); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(neighbor_three_cn.public_key(), neighbor_four_us.public_key()); + subject.user_exit_preferences.db_countries = subject.init_db_countries(); + let exit_locations_by_priority = vec![ExitLocation { + country_codes: vec!["FR".to_string(), "US".to_string()], + priority: 1, + }]; + for exit_location in &exit_locations_by_priority { + subject.enrich_exit_countries(&exit_location.country_codes); + } + subject.user_exit_preferences.fallback_preference = + FallbackPreference::ExitCountryNoFallback; + subject.user_exit_preferences.locations_opt = Some(exit_locations_by_priority); + let tree_hop_db_countries = subject.user_exit_preferences.db_countries.clone(); + let tree_hops_exit_countries = subject.user_exit_preferences.exit_countries.clone(); + let config_msg_two_hops = ConfigChangeMsg { + change: ConfigChange::UpdateMinHops(Hops::TwoHops), + }; + let config_msg_four_hops = ConfigChangeMsg { + change: ConfigChange::UpdateMinHops(Hops::FourHops), + }; + let peer_actors = peer_actors_builder().build(); + subject.handle_bind_message(BindMessage { peer_actors }); + + subject.handle_config_change_msg(config_msg_two_hops); + let two_hops_exit_countries = subject.user_exit_preferences.exit_countries.clone(); + let two_hops_db_countries = subject.user_exit_preferences.db_countries.clone(); + subject.handle_config_change_msg(config_msg_four_hops); + + let four_hops_exit_countries = subject.user_exit_preferences.exit_countries.clone(); + let four_hops_db_countries = subject.user_exit_preferences.db_countries; + assert_eq!( + tree_hop_db_countries, + vec!["CN".to_string(), "US".to_string()] + ); + assert_eq!(tree_hops_exit_countries, vec!["US".to_string()]); + assert_eq!( + two_hops_db_countries, + vec!["CN".to_string(), "FR".to_string(), "US".to_string()] + ); + assert_eq!( + two_hops_exit_countries, + vec!["US".to_string(), "FR".to_string()] + ); + assert_eq!(four_hops_db_countries, vec!["US".to_string()]); + assert_eq!(four_hops_exit_countries, vec!["US".to_string()]); + } + #[test] fn neighborhood_handles_config_change_msg() { assert_handling_of_config_change_msg( @@ -3618,7 +3741,6 @@ mod tests { init_test_logging(); let mut subject = make_standard_subject(); subject.logger = Logger::new("ConfigChange"); - subject.handle_config_change_msg(msg); assertions(&subject); @@ -3662,15 +3784,15 @@ mod tests { let request = UiSetExitLocationRequest { fallback_routing: true, exit_locations: vec![ - CountryCodes { + CountryGroups { country_codes: vec!["CZ".to_string(), "SK".to_string()], priority: 1, }, - CountryCodes { + CountryGroups { country_codes: vec!["AT".to_string(), "DE".to_string()], priority: 2, }, - CountryCodes { + CountryGroups { country_codes: vec!["PL".to_string()], priority: 3, }, @@ -3678,11 +3800,11 @@ mod tests { show_countries: false, }; let message = NodeFromUiMessage { - client_id: 0, - body: request.tmb(0), + client_id: 123, + body: request.tmb(234), }; let system = System::new(test_name); - let (ui_gateway, _, _) = make_recorder(); + let (ui_gateway, _recorder, arc_recorder) = make_recorder(); let mut subject = make_standard_subject(); subject.logger = Logger::new(test_name); let cz = &mut make_node_record(3456, true); @@ -3733,68 +3855,38 @@ mod tests { FallbackPreference::ExitCountryWithFallback ); assert_eq!( - neighborhood - .neighborhood_database - .node_by_key(&cz_public_key) - .unwrap() - .metadata - .country_undesirability, + neighborhood.get_node_country_undesirability(&cz_public_key), UNREACHABLE_COUNTRY_PENALTY, - "cz We expecting {}, country is too close to be exit", + "cz We expect {}, 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, + neighborhood.get_node_country_undesirability(&us_public_key), UNREACHABLE_COUNTRY_PENALTY, - "us We expecting {}, country is considered for exit location in fallback", + "us We expect {}, 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, + neighborhood.get_node_country_undesirability(&sk_public_key), 0u32, - "sk We expecting 0, country is with Priority: 1" + "sk We expect 0, country is with Priority: 1" ); assert_eq!( - neighborhood - .neighborhood_database - .node_by_key(&de_public_key) - .unwrap() - .metadata - .country_undesirability, + neighborhood.get_node_country_undesirability(&de_public_key), UNREACHABLE_COUNTRY_PENALTY, - "de We expecting {}, country is too close to be exit", + "de We expect {}, 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, + neighborhood.get_node_country_undesirability(&at_public_key), 1 * COUNTRY_UNDESIRABILITY_FACTOR, - "at We expecting {}, country is with Priority: 2", + "at We expect {}, country is with Priority: 2", 1 * COUNTRY_UNDESIRABILITY_FACTOR ); assert_eq!( - neighborhood - .neighborhood_database - .node_by_key(&pl_public_key) - .unwrap() - .metadata - .country_undesirability, + neighborhood.get_node_country_undesirability(&pl_public_key), 2 * COUNTRY_UNDESIRABILITY_FACTOR, - "pl We expecting {}, country is with Priority: 3", + "pl We expect {}, country is with Priority: 3", 2 * COUNTRY_UNDESIRABILITY_FACTOR ); }), @@ -3807,30 +3899,44 @@ mod tests { System::current().stop(); system.run(); + let recorder_result = arc_recorder.lock().unwrap(); + assert_eq!( + recorder_result.get_record::(0).body, + MessageBody { + opcode: "exitLocation".to_string(), + path: MessagePath::Conversation(234), + payload: Ok("{\"fallbackRouting\":true,\"exitCountrySelection\":[{\"CountryGroups\":[\"CZ\",\"SK\"],\"priority\":1},{\"CountryGroups\":[\"AT\",\"DE\"],\"priority\":2},{\"CountryGroups\":[\"PL\"],\"priority\":3}],\"exitCountries\":null,\"missingCountries\":[\"CZ\",\"DE\"]}".to_string()) + } + ); + assert_eq!( + recorder_result.get_record::(0).target, + MessageTarget::ClientId(123) + ); 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;" + &"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() { + fn no_exit_location_is_set_if_desired_country_codes_not_present_in_neighborhood_with_fallback_routing_set( + ) { 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 { + exit_locations: vec![CountryGroups { country_codes: vec!["CZ".to_string(), "SK".to_string(), "IN".to_string()], priority: 1, }], show_countries: false, }; let message = NodeFromUiMessage { - client_id: 0, - body: request.tmb(0), + client_id: 234, + body: request.tmb(123), }; let system = System::new(test_name); let (ui_gateway, _recorder, arc_recorder) = make_recorder(); @@ -3876,7 +3982,7 @@ mod tests { 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!(neighborhood.user_exit_preferences.exit_countries.is_empty()); assert_eq!( neighborhood.user_exit_preferences.locations_opt, Some(vec![ExitLocation { @@ -3898,69 +4004,39 @@ mod tests { FallbackPreference::ExitCountryWithFallback ); assert_eq!( - neighborhood - .neighborhood_database - .node_by_key(&es_public_key) - .unwrap() - .metadata - .country_undesirability, + neighborhood.get_node_country_undesirability(&es_public_key), UNREACHABLE_COUNTRY_PENALTY, - "es We expecting {}, country is too close to be exit", + "es We expect {}, 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, + neighborhood.get_node_country_undesirability(&us_public_key), UNREACHABLE_COUNTRY_PENALTY, - "us We expecting {}, country is considered for exit location in fallback", + "us We expect {}, 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, + neighborhood.get_node_country_undesirability(&hu_public_key), UNREACHABLE_COUNTRY_PENALTY, - "hu We expecting {}, country is too close to be exit", + "hu We expect {}, 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, + neighborhood.get_node_country_undesirability(&de_public_key), UNREACHABLE_COUNTRY_PENALTY, - "de We expecting {}, country is too close to be exit", + "de We expect {}, 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, + neighborhood.get_node_country_undesirability(&at_public_key), UNREACHABLE_COUNTRY_PENALTY, - "at We expecting {}, country is considered for exit location in fallback", + "at We expect {}, 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, + neighborhood.get_node_country_undesirability(&pl_public_key), UNREACHABLE_COUNTRY_PENALTY, - "pl We expecting {}, country is too close to be exit", + "pl We expect {}, country is too close to be exit", UNREACHABLE_COUNTRY_PENALTY ); }), @@ -3968,29 +4044,34 @@ mod tests { subject_addr.try_send(BindMessage { peer_actors }).unwrap(); subject_addr.try_send(message).unwrap(); - subject_addr.try_send(assertion_msg).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((9223372036854775817, "CZ, SK, IN".to_string(),)) + exit_location_recording + .get_record::(0) + .body, + MessageBody { + opcode: "exitLocation".to_string(), + path: MessagePath::Conversation(123), + payload: Err((9223372036854775816, "CZ, SK, IN".to_string(),)) + } + ); + assert_eq!( + exit_location_recording + .get_record::(0) + .target, + MessageTarget::ClientId(234) ); 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;", + &"Country Codes: [\"CZ\", \"SK\", \"IN\"] - Priority: 1", &format!( "WARN: {}: Exit Location: following desired countries are missing in Neighborhood [\"CZ\", \"SK\", \"IN\"]", test_name @@ -4006,11 +4087,11 @@ mod tests { let request = UiSetExitLocationRequest { fallback_routing: false, exit_locations: vec![ - CountryCodes { + CountryGroups { country_codes: vec!["CZ".to_string()], priority: 1, }, - CountryCodes { + CountryGroups { country_codes: vec!["FR".to_string()], priority: 2, }, @@ -4021,82 +4102,68 @@ mod tests { client_id: 8765, body: request.tmb(1234), }; + 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 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, true); + let standard_node_1 = &make_node_record(4567, true); let fr = &mut make_node_record(5678, true); fr.inner.country_code_opt = Some("FR".to_string()); - let t = &make_node_record(7777, true); - let db = &mut subject.neighborhood_database.clone(); + let standard_node_2 = &make_node_record(7777, true); + let root_node = subject.neighborhood_database.root().clone(); + let db = &mut subject.neighborhood_database; db.add_node(cz.clone()).unwrap(); - db.add_node(t.clone()).unwrap(); - db.add_node(r.clone()).unwrap(); + db.add_node(standard_node_2.clone()).unwrap(); + db.add_node(standard_node_1.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); + dual_edge(&root_node, cz); + dual_edge(cz, standard_node_2); + dual_edge(cz, standard_node_1); + dual_edge(standard_node_1, 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 sn_1_public_key = standard_node_1.inner.public_key.clone(); let fr_public_key = fr.inner.public_key.clone(); - let t_public_key = t.inner.public_key.clone(); + let sn_2_public_key = standard_node_2.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, + neighborhood.get_node_country_undesirability(&cz_public_key), 0u32, - "CZ - We expecting zero, country is with Priority: 1" + "CZ - We expect zero, country is with Priority: 1" ); assert_eq!( - neighborhood - .neighborhood_database - .node_by_key(&r_public_key) - .unwrap() - .metadata - .country_undesirability, + neighborhood.get_node_country_undesirability(&sn_1_public_key), 0u32, - "We expecting 0, country is not considered for exit location, so country_undesirability doesn't matter" + "We expect 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, + neighborhood.get_node_country_undesirability(&fr_public_key), 1 * COUNTRY_UNDESIRABILITY_FACTOR, - "FR - We expecting {}, country is with Priority: 2", + "FR - We expect {}, country is with Priority: 2", 1 * COUNTRY_UNDESIRABILITY_FACTOR ); assert_eq!( - neighborhood - .neighborhood_database - .node_by_key(&t_public_key) - .unwrap() - .metadata - .country_undesirability, + neighborhood.get_node_country_undesirability(&sn_2_public_key), 0u32, - "We expecting 0, country is not considered for exit location, so country_undesirability doesn't matter" + "We expect 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()] @@ -4107,60 +4174,31 @@ mod tests { ); }), }; - 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 r_public_key_2 = standard_node_1.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 t_public_key_2 = standard_node_2.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, + neighborhood.get_node_country_undesirability(&cz_public_key_2), 0u32, - "We expecting zero, exit_location was unset" + "We expect zero, exit_location was unset" ); assert_eq!( - neighborhood - .neighborhood_database - .node_by_key(&r_public_key_2) - .unwrap() - .metadata - .country_undesirability, + neighborhood.get_node_country_undesirability(&r_public_key_2), 0u32, - "We expecting zero, exit_location was unset" + "We expect zero, exit_location was unset" ); assert_eq!( - neighborhood - .neighborhood_database - .node_by_key(&fr_public_key_2) - .unwrap() - .metadata - .country_undesirability, + neighborhood.get_node_country_undesirability(&fr_public_key_2), 0u32, - "We expecting zero, exit_location was unset" + "We expect zero, exit_location was unset" ); assert_eq!( - neighborhood - .neighborhood_database - .node_by_key(&t_public_key_2) - .unwrap() - .metadata - .country_undesirability, + neighborhood.get_node_country_undesirability(&t_public_key_2), 0u32, - "We expecting zero, exit_location was unset" + "We expect zero, exit_location was unset" ); assert_eq!( neighborhood.user_exit_preferences.exit_countries.is_empty(), @@ -4178,9 +4216,6 @@ mod tests { 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) @@ -4191,13 +4226,12 @@ mod tests { 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.body, UiSetExitLocationResponse { fallback_routing: false, - exit_locations: vec![ + exit_country_selection: vec![ ExitLocation { country_codes: vec!["CZ".to_string()], priority: 1 @@ -4218,7 +4252,7 @@ mod tests { target: MessageTarget::ClientId(6543), body: UiSetExitLocationResponse { fallback_routing: true, - exit_locations: vec![], + exit_country_selection: vec![], exit_countries: None, missing_countries: vec![], } @@ -4227,7 +4261,7 @@ mod tests { ); 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;", + "INFO: {}: Fallback Routing NOT set. Exit location set: Country Codes: [\"CZ\"] - Priority: 1; Country Codes: [\"FR\"] - Priority: 2", test_name ), &format!( @@ -4414,53 +4448,13 @@ mod tests { All these Nodes are standard-mode. L is the root Node. */ #[test] - fn find_exit_location_test() { + fn find_exit_locations_in_packed_grid() { 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 keys = make_db_with_regular_5_x_5_network(db); + designate_root_node(db, keys.get("l").unwrap()); - let mut exit_nodes = subject.find_exit_location(&l, 3, 10_000); + let mut exit_nodes = subject.find_exit_locations(keys.get("l").unwrap(), 3); let total_exit_nodes = exit_nodes.len(); exit_nodes.sort(); @@ -4471,7 +4465,7 @@ mod tests { } #[test] - fn find_exit_locations_in_row_structure_test() { + fn find_exit_locations_in_row_structure() { let mut subject = make_standard_subject(); let db = &mut subject.neighborhood_database; let mut generator = 1000; @@ -4499,14 +4493,9 @@ mod tests { 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 mut exit_nodes = subject.find_exit_location(&n1, 3, 10_000); + let mut exit_nodes = subject.find_exit_locations(&n1, 3); let total_exit_nodes = exit_nodes.len(); exit_nodes.sort(); @@ -4596,7 +4585,7 @@ mod tests { */ #[test] - fn route_optimization_test() { + fn route_optimization_by_serving_rates() { let mut subject = make_standard_subject(); let db = &mut subject.neighborhood_database; let (recipient, _) = make_node_to_ui_recipient(); @@ -4606,58 +4595,34 @@ mod tests { 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(); - 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 keys = make_db_with_regular_5_x_5_network(db); + designate_root_node(db, keys.get("l").unwrap()); 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) + .find_best_route_segment( + &keys.get("l").unwrap(), + Some(&keys.get("n").unwrap()), + 3, + 10000, + RouteDirection::Back, + None, + ) .unwrap(); let after = Instant::now(); - assert_eq!(route, vec![&l, &g, &h, &i, &n]); // Cheaper than [&l, &q, &r, &s, &n] + assert_eq!( + route, + vec![ + keys.get("l").unwrap(), + keys.get("g").unwrap(), + keys.get("h").unwrap(), + keys.get("i").unwrap(), + keys.get("n").unwrap() + ] + ); // Cheaper than [&l, &q, &r, &s, &n] let interval = after.duration_since(before); assert!( interval.as_millis() <= 100, @@ -4680,89 +4645,49 @@ mod tests { | | | | | U---V---W---X---Y - All these Nodes are standard-mode. L is the root Node. + All these Nodes are standard-mode. L is the root Node. C and T are "CZ" standard nodes */ #[test] - fn route_optimization_country_codes() { + fn route_optimization_with_user_exit_preferences() { let mut subject = make_standard_subject(); + subject.min_hops = Hops::TwoHops; 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 { + exit_locations: vec![CountryGroups { 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()); - + let keys = make_db_with_regular_5_x_5_network(db); + db.node_by_key_mut(&keys.get("c").unwrap()) + .unwrap() + .inner + .country_code_opt = Some("CZ".to_string()); + db.node_by_key_mut(&keys.get("t").unwrap()) + .unwrap() + .inner + .country_code_opt = Some("CZ".to_string()); + let control_db = db.clone(); + designate_root_node(db, &keys.get("l").unwrap()); subject.handle_exit_location_message(message, 0, 0); let before = Instant::now(); - let route_cz = - subject.find_best_route_segment(&l, None, 3, 10000, RouteDirection::Over, None); + let route_cz = subject.find_best_route_segment( + &keys.get("l").unwrap(), + None, + 3, + 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(), - ); + let exit_node = control_db.node_by_key(&route_cz.as_ref().unwrap().last().unwrap()); assert_eq!( exit_node.unwrap().inner.country_code_opt, Some("CZ".to_string()) @@ -4778,14 +4703,14 @@ mod tests { /* Database: - P---q---R - - Test is written from the standpoint of P. Node q is non-routing. + root_key---c_au---b_fr + | + a_fr + Test is written from the standpoint of root_key. */ #[test] - fn find_best_segment_traces_unreachable_country_code_exit_node() { - init_test_logging(); + fn exit_node_not_found_due_to_country_code_strict_requirement() { let mut subject = make_standard_subject(); let (recipient, _) = make_node_to_ui_recipient(); subject.node_to_ui_recipient_opt = Some(recipient); @@ -4793,24 +4718,24 @@ mod tests { FallbackPreference::ExitCountryWithFallback; let message = UiSetExitLocationRequest { fallback_routing: false, - exit_locations: vec![CountryCodes { + exit_locations: vec![CountryGroups { 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); + let root_key = &db.root_mut().public_key().clone(); + let a_fr = &db.add_node(make_node_record(2345, true)).unwrap(); + let b_fr = &db.add_node(make_node_record(5678, true)).unwrap(); + let c_au = &db.add_node(make_node_record(1234, true)).unwrap(); + db.add_arbitrary_full_neighbor(root_key, c_au); + db.add_arbitrary_full_neighbor(c_au, b_fr); + db.add_arbitrary_full_neighbor(c_au, a_fr); subject.handle_exit_location_message(message, 0, 0); let route_cz = - subject.find_best_route_segment(p, None, 2, 10000, RouteDirection::Over, None); + subject.find_best_route_segment(root_key, None, 2, 10000, RouteDirection::Over, None); assert_eq!(route_cz, None); } @@ -4818,59 +4743,53 @@ mod tests { #[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 + let root_key = &subject .neighborhood_database .root_mut() .public_key() .clone(); - let a = &subject - .neighborhood_database - .add_node(make_node_record(2345, true)) - .unwrap(); - let b = &subject + let mut a_fr_node = make_node_record(2345, true); + a_fr_node.inner.rate_pack.exit_byte_rate = 1; + a_fr_node.inner.rate_pack.exit_service_rate = 1; + let mut c_au_node = make_node_record(1234, true); + c_au_node.inner.rate_pack.exit_byte_rate = 10; + c_au_node.inner.rate_pack.exit_service_rate = 10; + let a_fr_key = &subject.neighborhood_database.add_node(a_fr_node).unwrap(); + let b_fr_key = &subject .neighborhood_database .add_node(make_node_record(5678, true)) .unwrap(); - let c = &subject - .neighborhood_database - .add_node(make_node_record(1234, true)) - .unwrap(); + let c_au_key = &subject.neighborhood_database.add_node(c_au_node).unwrap(); subject .neighborhood_database - .add_arbitrary_full_neighbor(p, b); + .add_arbitrary_full_neighbor(root_key, b_fr_key); subject .neighborhood_database - .add_arbitrary_full_neighbor(b, c); + .add_arbitrary_full_neighbor(b_fr_key, c_au_key); subject .neighborhood_database - .add_arbitrary_full_neighbor(b, a); + .add_arbitrary_full_neighbor(b_fr_key, a_fr_key); subject .neighborhood_database - .add_arbitrary_full_neighbor(a, c); + .add_arbitrary_full_neighbor(a_fr_key, c_au_key); 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 { + exit_locations: vec![CountryGroups { country_codes: vec!["AU".to_string()], priority: 1, }], show_countries: false, }; subject.handle_exit_location_message(message, 0, 0); + let subject_min_hops = 2; let route_au = - subject.find_best_route_segment(p, None, 2, 10000, RouteDirection::Over, None); + subject.find_best_route_segment(root_key, None, subject_min_hops, 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(), - ); + let exit_node = cdb.node_by_key(&route_au.as_ref().unwrap().last().unwrap()); assert_eq!( exit_node.unwrap().inner.country_code_opt, Some("AU".to_string()) @@ -4880,41 +4799,41 @@ mod tests { #[test] fn route_for_fr_country_code_is_constructed_without_fallback_routing() { let mut subject = make_standard_subject(); - let p = &subject + let root_key = &subject .neighborhood_database .root_mut() .public_key() .clone(); - let a = &subject + let a_fr = &subject .neighborhood_database .add_node(make_node_record(2345, true)) .unwrap(); - let b = &subject + let b_fr = &subject .neighborhood_database .add_node(make_node_record(5678, true)) .unwrap(); - let c = &subject + let c_au = &subject .neighborhood_database .add_node(make_node_record(1234, true)) .unwrap(); subject .neighborhood_database - .add_arbitrary_full_neighbor(p, b); + .add_arbitrary_full_neighbor(root_key, b_fr); subject .neighborhood_database - .add_arbitrary_full_neighbor(b, c); + .add_arbitrary_full_neighbor(b_fr, c_au); subject .neighborhood_database - .add_arbitrary_full_neighbor(b, a); + .add_arbitrary_full_neighbor(b_fr, a_fr); subject .neighborhood_database - .add_arbitrary_full_neighbor(a, c); + .add_arbitrary_full_neighbor(a_fr, c_au); 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 { + exit_locations: vec![CountryGroups { country_codes: vec!["FR".to_string()], priority: 1, }], @@ -4923,15 +4842,9 @@ mod tests { subject.handle_exit_location_message(message, 0, 0); let route_fr = - subject.find_best_route_segment(p, None, 2, 10000, RouteDirection::Over, None); + subject.find_best_route_segment(root_key, 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(), - ); + let exit_node = cdb.node_by_key(&route_fr.as_ref().unwrap().last().unwrap()); assert_eq!( exit_node.unwrap().inner.country_code_opt, Some("FR".to_string()) @@ -5034,7 +4947,7 @@ mod tests { TestLogHandler::new().exists_log_containing( "TRACE: Neighborhood: Node with PubKey 0x02030405 \ failed to reach host \"hostname.com\" during ExitRequest; \ - Undesirability: 2350745 + 100000000 = 102350745", + Undesirability: 2350745 + 100000000 + 0 = 102350745", ); } @@ -5945,60 +5858,6 @@ 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 mut new_neighbor = make_node_record(2345, false); - new_neighbor.inner.country_code_opt = Some("FR".to_string()); - 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.set_root_key(&first_key); - 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(); @@ -7569,15 +7428,6 @@ mod tests { subject } - fn make_debut_subject() -> Neighborhood { - let root_node = make_global_cryptde_node_record(9999, true); - let mut subject = neighborhood_from_nodes(&root_node, None); - let persistent_config = - PersistentConfigurationMock::new().set_past_neighbors_result(Ok(())); - subject.persistent_config_opt = Some(Box::new(persistent_config)); - subject - } - fn make_o_r_e_subject() -> (NodeRecord, NodeRecord, NodeRecord, Neighborhood) { let mut subject = make_standard_subject(); let o = &subject.neighborhood_database.root().clone(); @@ -7804,4 +7654,93 @@ mod tests { neighborhood } + + /* + 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 + */ + fn make_db_with_regular_5_x_5_network( + db: &mut NeighborhoodDatabase, + ) -> HashMap<&'static str, PublicKey> { + 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 (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 keypairs = [ + ("a", a), + ("b", b), + ("c", c), + ("d", d), + ("e", e), + ("f", f), + ("g", g), + ("h", h), + ("i", i), + ("j", j), + ("k", k), + ("l", l), + ("m", m), + ("n", n), + ("o", o), + ("p", p), + ("q", q), + ("r", r), + ("s", s), + ("t", t), + ("u", u), + ("v", v), + ("w", w), + ("x", x), + ("y", y), + ]; + HashMap::from_iter(keypairs) + } + + fn 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); + } } diff --git a/node/src/neighborhood/neighborhood_database.rs b/node/src/neighborhood/neighborhood_database.rs index 63fc633ce..ddb0fb879 100644 --- a/node/src/neighborhood/neighborhood_database.rs +++ b/node/src/neighborhood/neighborhood_database.rs @@ -410,7 +410,6 @@ mod tests { let last_update = subject.root().metadata.last_update; this_node.metadata.last_update = last_update; - assert_eq!(subject.this_node, this_node.public_key().clone()); assert_eq!( subject.by_public_key, @@ -446,7 +445,6 @@ mod tests { let last_update = subject.root().metadata.last_update; this_node.metadata.last_update = last_update; - assert_eq!(subject.this_node, this_node.public_key().clone()); assert_eq!( subject.by_public_key, @@ -506,7 +504,6 @@ mod tests { #[test] fn node_by_key_works() { let mut this_node = make_node_record(1234, true); - let one_node = make_node_record(4567, true); let another_node = make_node_record(5678, true); let mut subject = NeighborhoodDatabase::new( @@ -519,14 +516,8 @@ mod tests { subject.add_node(one_node.clone()).unwrap(); let this_pubkey = this_node.public_key(); - let updated_record = subject - .by_public_key - .iter() - .filter(|(pubkey, _node_record)| *pubkey == this_pubkey) - .exactly_one() - .unwrap(); - this_node.metadata.last_update = updated_record.1.metadata.last_update; - + let updated_record = subject.node_by_key(this_pubkey).unwrap(); + this_node.metadata.last_update = updated_record.metadata.last_update; assert_eq!( subject.node_by_key(this_node.public_key()).unwrap().clone(), this_node @@ -554,14 +545,8 @@ mod tests { subject.add_node(one_node.clone()).unwrap(); let this_pubkey = this_node.public_key(); - let updated_record = subject - .by_public_key - .iter() - .filter(|(pubkey, _node_record)| *pubkey == this_pubkey) - .exactly_one() - .unwrap(); - this_node.metadata.last_update = updated_record.1.metadata.last_update; - + let updated_record = subject.node_by_key(this_pubkey).unwrap(); + this_node.metadata.last_update = updated_record.metadata.last_update; assert_eq!( subject .node_by_ip(&this_node.node_addr_opt().unwrap().ip_addr()) @@ -595,20 +580,19 @@ mod tests { ); subject.add_node(node_a.clone()).unwrap(); subject.add_node(node_b.clone()).unwrap(); - let mut iterator: u16 = 7890; + let mut ipnumber: 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); + let (seg1, seg2, seg3, seg4) = make_segments(ipnumber); node.metadata.node_addr_opt = Some(NodeAddr::new( &make_segmented_ip(seg1, seg2, seg3, seg4), - &[iterator], + &[ipnumber], )); - keys_nums.push((node.inner.public_key.clone(), iterator)); - iterator += 1; + keys_nums.push((node.inner.public_key.clone(), ipnumber)); + ipnumber += 1; } - for (pub_key, num) in keys_nums { let (seg1, seg2, seg3, seg4) = make_segments(num); assert_eq!( @@ -865,7 +849,7 @@ mod tests { ); assert_string_contains( &result, - "\"AwQFBg\" [label=\"AR v0 FR\\nAwQFBg\\n3.4.5.6:3456\"];", + "\"AwQFBg\" [label=\"AR v0 CN\\nAwQFBg\\n3.4.5.6:3456\"];", ); assert_string_contains( &result, @@ -903,13 +887,8 @@ mod tests { subject.new_public_ip(new_public_ip); let this_pubkey = this_node.public_key(); - let updated_record = subject - .by_public_key - .iter() - .filter(|(pubkey, _node_record)| *pubkey == this_pubkey) - .exactly_one() - .unwrap(); - old_node.metadata.last_update = updated_record.1.metadata.last_update; + let updated_record = subject.node_by_key(this_pubkey).unwrap(); + old_node.metadata.last_update = updated_record.metadata.last_update; let mut new_node = subject.root().clone(); assert_eq!(subject.node_by_ip(&new_public_ip), Some(&new_node)); diff --git a/node/src/neighborhood/node_location.rs b/node/src/neighborhood/node_location.rs index 7e960fdc2..6142b5cc6 100644 --- a/node/src/neighborhood/node_location.rs +++ b/node/src/neighborhood/node_location.rs @@ -40,6 +40,7 @@ mod tests { #[test] fn construct_node_record_metadata_with_free_world_bit() { + //TODO check in From impl for AGR that construction of metadata contains proper country_code and fwb, then delete this test let mut metadata = NodeRecordMetadata::new(); metadata.node_location_opt = get_node_location(Some(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)))); assert_eq!( diff --git a/node/src/neighborhood/node_record.rs b/node/src/neighborhood/node_record.rs index 7a29c0b1e..8502d444f 100644 --- a/node/src/neighborhood/node_record.rs +++ b/node/src/neighborhood/node_record.rs @@ -1,9 +1,10 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::neighborhood::gossip::GossipNodeRecord; +use crate::neighborhood::gossip::{ + regenerate_signed_gossip, AccessibleGossipRecord, GossipNodeRecord, +}; use crate::neighborhood::neighborhood_database::{NeighborhoodDatabase, NeighborhoodDatabaseError}; use crate::neighborhood::node_location::{get_node_location, NodeLocation}; -use crate::neighborhood::{regenerate_signed_gossip, AccessibleGossipRecord}; use crate::sub_lib::cryptde::{CryptDE, CryptData, PlainData, PublicKey}; use crate::sub_lib::neighborhood::{NodeDescriptor, RatePack}; use crate::sub_lib::node_addr::NodeAddr; @@ -347,8 +348,12 @@ pub struct NodeRecordMetadata { pub node_addr_opt: Option, pub unreachable_hosts: HashSet, pub node_location_opt: Option, + // country_undesirability is used in combination with FallbackRouting. If FallbackRouting is set + // to false, we do not consider the undesirability of countries other than those selected for exit. + // Therefore, we use a value of 0 for exit nodes in countries that are not considered for exit. pub country_undesirability: u32, //TODO introduce scores for latency #582 and reliability #583 + //TODO introduce check for node_location_opt, to verify full neighbors country code (we know his IP, so we can verify it) } impl NodeRecordMetadata { @@ -381,11 +386,12 @@ mod tests { let mut node_record_wo_location = make_node_record(2222, false); node_record_wo_location.inner.accepts_connections = false; let no_location_db = db_from_node(&node_record_wo_location); - let no_location_gossip = - GossipBuilder::new(&no_location_db).node(node_record_wo_location.public_key(), false); + let no_location_gossip = GossipBuilder::new(&no_location_db) + .node(node_record_wo_location.public_key(), false) + .build(); let nr_wo_location = - NodeRecord::try_from(no_location_gossip.build().node_records.first().unwrap()).unwrap(); + NodeRecord::try_from(no_location_gossip.node_records.first().unwrap()).unwrap(); assert_eq!(nr_wo_location.inner.country_code_opt, None); } diff --git a/node/src/node_configurator/unprivileged_parse_args_configuration.rs b/node/src/node_configurator/unprivileged_parse_args_configuration.rs index e124b150c..4238bd8d5 100644 --- a/node/src/node_configurator/unprivileged_parse_args_configuration.rs +++ b/node/src/node_configurator/unprivileged_parse_args_configuration.rs @@ -853,7 +853,6 @@ mod tests { Ok(NeighborhoodConfig { mode: NeighborhoodMode::Standard(node_addr, _, _), min_hops: Hops::ThreeHops, - .. }) => node_addr, x => panic!("Wasn't expecting {:?}", x), }; diff --git a/node/src/test_utils/neighborhood_test_utils.rs b/node/src/test_utils/neighborhood_test_utils.rs index 5c3904ad7..1b0af210f 100644 --- a/node/src/test_utils/neighborhood_test_utils.rs +++ b/node/src/test_utils/neighborhood_test_utils.rs @@ -1,10 +1,12 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::bootstrapper::BootstrapperConfig; -use crate::neighborhood::gossip::{GossipBuilder, GossipNodeRecord, Gossip_0v1}; +use crate::neighborhood::gossip::{ + AccessibleGossipRecord, GossipBuilder, GossipNodeRecord, Gossip_0v1, +}; use crate::neighborhood::neighborhood_database::NeighborhoodDatabase; use crate::neighborhood::node_location::NodeLocation; use crate::neighborhood::node_record::{NodeRecord, NodeRecordInner_0v1, NodeRecordInputs}; -use crate::neighborhood::{AccessibleGossipRecord, Neighborhood, DEFAULT_MIN_HOPS}; +use crate::neighborhood::{Neighborhood, DEFAULT_MIN_HOPS}; use crate::sub_lib::cryptde::PublicKey; use crate::sub_lib::cryptde::{CryptDE, PlainData}; use crate::sub_lib::cryptde_null::CryptDENull; @@ -26,8 +28,8 @@ lazy_static! { pub static ref COUNTRY_CODE_DIGEST: Vec<(IpAddr, String, bool)> = vec![ ( IpAddr::V4(Ipv4Addr::new(123, 123, 123, 123)), - "FR".to_string(), - true + "CN".to_string(), + false ), ( IpAddr::V4(Ipv4Addr::new(0, 0, 0, 123)), @@ -83,7 +85,7 @@ pub fn make_node_record(n: u16, has_ip: bool) -> NodeRecord { let key = PublicKey::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 (_ip, country_code, free_world_bit) = pick_country_code_record(n); let location_opt = match has_ip { true => match country_code.is_empty() { false => Some(NodeLocation { diff --git a/node/tests/ui_gateway_test.rs b/node/tests/ui_gateway_test.rs index 56893a277..eb3658f9c 100644 --- a/node/tests/ui_gateway_test.rs +++ b/node/tests/ui_gateway_test.rs @@ -15,7 +15,8 @@ use masq_lib::test_utils::utils::ensure_node_home_directory_exists; use masq_lib::utils::{add_chain_specific_directory, find_free_port}; use std::net::TcpStream; use std::thread; -use std::time::Duration; +use std::time::{Duration, SystemTime}; +use sysinfo::{ProcessExt, System, SystemExt}; use utils::CommandConfig; #[test] @@ -57,7 +58,7 @@ fn ui_requests_something_and_gets_corresponding_response() { #[test] fn log_broadcasts_are_correctly_received_integration() { - thread::sleep(Duration::from_secs(5)); + wait_for_masq_node_ends(); fdlimit::raise_fd_limit(); let port = find_free_port(); let mut node = utils::MASQNode::start_standard( @@ -96,11 +97,32 @@ fn log_broadcasts_are_correctly_received_integration() { node.wait_for_exit(); } +fn wait_for_masq_node_ends() { + let mut system = System::new_all(); + let deadline = SystemTime::now() + Duration::from_secs(5); + loop { + if SystemTime::now() > deadline { + panic!("Previous instance of MASQNode does not stops"); + } + system.refresh_all(); + if system + .processes() + .into_iter() + .find(|(_, process)| process.name().contains("MASQNode")) + .is_none() + { + break; + } + thread::sleep(Duration::from_millis(500)); + } +} + #[test] fn daemon_does_not_allow_node_to_keep_his_client_alive_integration() { //Daemon's probe to check if the Node is alive causes an unwanted new reference //for the Daemon's client, so we need to make the Daemon send a close message //breaking any reference to him immediately + wait_for_masq_node_ends(); fdlimit::raise_fd_limit(); let data_directory = ensure_node_home_directory_exists( "ui_gateway_test", @@ -182,6 +204,7 @@ fn daemon_does_not_allow_node_to_keep_his_client_alive_integration() { #[test] fn cleanup_after_deceased_clients_integration() { + wait_for_masq_node_ends(); fdlimit::raise_fd_limit(); let port = find_free_port(); let mut node = utils::MASQNode::start_standard(