From 9c0d11642be153f586ffafb395364c5ad3ebb0f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Thu, 7 Nov 2024 19:39:48 +0100 Subject: [PATCH] working exit country selection wihtout fallback, introduces new structure db_countries and find_exit_location fn from GH-469 --- node/src/neighborhood/gossip_acceptor.rs | 6 + node/src/neighborhood/mod.rs | 198 +++++++++++++++++------ node/src/neighborhood/node_record.rs | 4 - 3 files changed, 157 insertions(+), 51 deletions(-) diff --git a/node/src/neighborhood/gossip_acceptor.rs b/node/src/neighborhood/gossip_acceptor.rs index 4b96914a5..445e73748 100644 --- a/node/src/neighborhood/gossip_acceptor.rs +++ b/node/src/neighborhood/gossip_acceptor.rs @@ -282,6 +282,7 @@ impl DebutHandler { let mut debuting_node = NodeRecord::from(debuting_agr); match user_exit_preferences_opt { Some(user_exit_preferences) => { + //TODO 788 check if country code is present in Neighborhood DB and if yes, perform assign country code to exit_countries without duplication user_exit_preferences.assign_nodes_country_undesirability(&mut debuting_node) } None => (), @@ -894,6 +895,7 @@ impl IntroductionHandler { let mut new_introducer = NodeRecord::from(introducer); //TODO 468 add country undesirability match user_exit_preferences_opt { + //TODO 788 check if country code is present in Neighborhood DB and if yes, perform assign country code to exit_countries without duplication Some(user_exit_preferences) => user_exit_preferences .assign_nodes_country_undesirability(&mut new_introducer), None => (), @@ -1148,6 +1150,7 @@ impl StandardGossipHandler { // TODO modify for country undesirability in node_record (make it mut) match user_exit_preferences_opt { Some(user_exit_preferences) => { + //TODO 788 check if country code is present in Neighborhood DB and if yes, perform assign country code to exit_countries without duplication user_exit_preferences.assign_nodes_country_undesirability(&mut node_record) } None => (), @@ -2076,6 +2079,7 @@ mod tests { country_codes: vec!["FR".to_string()], priority: 2, }]), + db_countries: vec!["FR".to_string()], }); let qualifies_result = subject.qualifies(&dest_db, &agrs, gossip_source); @@ -2472,6 +2476,7 @@ mod tests { country_codes: vec!["FR".to_string()], priority: 1, }]), + db_countries: vec!["FR".to_string()], }); neighborhood_metadata.cpm_recipient = cpm_recipient; let system = System::new("test"); @@ -3069,6 +3074,7 @@ mod tests { country_codes: vec!["CZ".to_string()], priority: 1, }]), + db_countries: vec!["CZ".to_string()], }); let result = subject.handle( diff --git a/node/src/neighborhood/mod.rs b/node/src/neighborhood/mod.rs index 66bb6b1ed..868241e79 100644 --- a/node/src/neighborhood/mod.rs +++ b/node/src/neighborhood/mod.rs @@ -23,7 +23,7 @@ use masq_lib::messages::{ use masq_lib::messages::{UiConnectionStatusResponse, UiShutdownRequest}; use masq_lib::ui_gateway::{MessageTarget, NodeFromUiMessage, NodeToUiMessage}; use masq_lib::utils::{exit_process, ExpectValue, NeighborhoodModeLight}; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::convert::TryFrom; use std::fmt::Debug; use std::net::{IpAddr, SocketAddr}; @@ -89,6 +89,11 @@ 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 ExitPreference { Nothing, @@ -102,6 +107,7 @@ pub struct UserExitPreferences { exit_countries: Vec, //if we cross number of countries used in one workflow, we want to change this member to HashSet exit_location_preference: ExitPreference, exit_locations_opt: Option>, + db_countries: Vec, } impl UserExitPreferences { @@ -111,6 +117,7 @@ impl UserExitPreferences { exit_countries: vec![], exit_location_preference: ExitPreference::Nothing, exit_locations_opt: None, + db_countries: vec![], } } @@ -1300,7 +1307,7 @@ impl Neighborhood { UndesirabilityType::Relay => node_record.inner.rate_pack.routing_charge(payload_size), UndesirabilityType::ExitRequest(_) => { node_record.inner.rate_pack.exit_charge(payload_size) - + node_record.country_code_exeption() + + node_record.metadata.country_undesirability as u64 } UndesirabilityType::ExitAndRouteResponse => { node_record.inner.rate_pack.exit_charge(payload_size) @@ -1374,6 +1381,7 @@ impl Neighborhood { direction, &mut minimum_undesirability, hostname_opt, + false ) .into_iter() .filter_map(|cr| match cr.undesirability <= minimum_undesirability { @@ -1396,6 +1404,7 @@ impl Neighborhood { direction: RouteDirection, minimum_undesirability: &mut i64, hostname_opt: Option<&str>, + research_neighborhood: bool, ) -> Vec> { if undesirability > *minimum_undesirability { return vec![]; @@ -1419,7 +1428,10 @@ impl Neighborhood { *minimum_undesirability = undesirability; } vec![ComputedRouteSegment::new(prefix, undesirability)] - } else if (hops_remaining == 0) && target_opt.is_none() { + } else if (hops_remaining == 0) && target_opt.is_none() && !research_neighborhood && + (self.user_exit_preferences.exit_location_preference == ExitPreference::Nothing || + self.user_exit_preferences.exit_countries.is_empty()) // TODO implement the emptying the self.user_exit_preferences.exit_countries on every change of the database when there is no longer any country desired by user in the database + { // don't continue a targetless search past the minimum hop count vec![] } else { @@ -1461,6 +1473,7 @@ impl Neighborhood { direction, minimum_undesirability, hostname_opt, + research_neighborhood, ) }) .collect() @@ -1535,6 +1548,47 @@ impl Neighborhood { undesirability + node_undesirability } + pub fn find_exit_location<'a>( + &'a self, + source: &'a PublicKey, + minimum_hops: usize, + payload_size: usize, + ) -> HashMap { + let root_key = self.neighborhood_database.root().public_key().to_owned(); + let mut minimum_undesirability = i64::MAX; + let initial_undesirability = + self.compute_initial_undesirability(&root_key, payload_size as u64, RouteDirection::Over); + let over_routes = self + .routing_engine( + vec![source], + initial_undesirability, + None, + minimum_hops, + payload_size, + RouteDirection::Over, + &mut minimum_undesirability, + None, + true + ); + + let mut result_exit: HashMap = HashMap::new(); + + over_routes.into_iter().for_each(|segment| { + let exit_node = segment.nodes[segment.nodes.len()-1]; + result_exit + .entry(exit_node.clone()) + .and_modify(|e| + e.routes.push((segment.nodes.clone(), segment.undesirability))) + .or_insert(ExitLocationsRoutes{ + routes: vec![( + segment.nodes.clone(), + segment.undesirability) + ] + } + ); + }); + result_exit + } fn handle_exit_location_message( &mut self, message: UiSetExitLocationRequest, @@ -1611,13 +1665,19 @@ impl Neighborhood { &mut self, message: &UiSetExitLocationRequest, ) -> Vec { + self.init_db_countries(); + println!("self.db_coutries: {:?}", self.user_exit_preferences.db_countries); message .to_owned() .exit_locations .into_iter() .map(|cc| { for code in &cc.country_codes { - self.user_exit_preferences.exit_countries.push(code.clone()); + //TODO 788 check if the country code is available in the DB for exit - make list of all the available countries in DB outside the loop and use it here + // push without duplication + if self.user_exit_preferences.db_countries.contains(code) { + self.user_exit_preferences.exit_countries.push(code.clone()); + } } ExitLocation { country_codes: cc.country_codes, @@ -1627,6 +1687,26 @@ impl Neighborhood { .collect() } + fn init_db_countries(&mut self) { + let root_key = self.neighborhood_database.keys().iter().next().expect("expected public key").to_owned().to_owned().to_owned(); + let min_hops = self.min_hops as usize; + let exit_locations_db = self.find_exit_location(&root_key, min_hops, 10_000usize).to_owned(); + println!("exit_locations_db {:#?}", exit_locations_db); + let mut db_countries = vec![]; + for (pub_key, _routes) in &exit_locations_db { + let node_opt = self.neighborhood_database.node_by_key(&pub_key); + match node_opt { + Some(node_record) => match &node_record.inner.country_code_opt { + Some(cc) => db_countries.push(cc.clone()), + _ => (), + }, + _ => () + } + }; + self.user_exit_preferences.db_countries = db_countries; + //self.user_exit_preferences.db_countries.dedup(); + } + fn handle_gossip_reply( &self, gossip: Gossip_0v1, @@ -3930,16 +4010,31 @@ mod tests { ); } - /* Complex testing of country_code undesirability on large network with aim to fin fallback routing and non fallback routing mechanisms */ + /* Complex testing of country_undesirability on large network with aim to find fallback routing and non fallback routing mechanisms + + Database: + + A---B---C---D---E + | | | | | + F---G---H---I---J + | | | | | + K---L---M---N---O + | | | | | + P---Q---R---S---T + | | | | | + U---V---W---X---Y + + All these Nodes are standard-mode. L is the root Node. + + */ #[test] fn route_optimization_country_codes() { let mut subject = make_standard_subject(); let db = &mut subject.neighborhood_database; let (recipient, _) = make_node_to_ui_recipient(); subject.node_to_ui_recipient_opt = Some(recipient); - subject.user_exit_preferences.exit_location_preference = ExitPreference::ExitCountryWithFallback; let message = UiSetExitLocationRequest { - fallback_routing: false, + fallback_routing: true, exit_locations: vec![CountryCodes { country_codes: vec!["CZ".to_string()], priority: 1, @@ -3985,54 +4080,55 @@ mod tests { 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()); println!("a {} - b {} - c {} - d {} - e {}", - db.node_by_key(&a).unwrap().inner.country_code_opt.as_ref().unwrap(), - db.node_by_key(&b).unwrap().inner.country_code_opt.as_ref().unwrap(), - db.node_by_key(&c).unwrap().inner.country_code_opt.as_ref().unwrap(), - db.node_by_key(&d).unwrap().inner.country_code_opt.as_ref().unwrap(), - db.node_by_key(&e).unwrap().inner.country_code_opt.as_ref().unwrap()); + db.node_by_key(&a).unwrap().inner.country_code_opt.as_ref().unwrap(), + db.node_by_key(&b).unwrap().inner.country_code_opt.as_ref().unwrap(), + db.node_by_key(&c).unwrap().inner.country_code_opt.as_ref().unwrap(), + db.node_by_key(&d).unwrap().inner.country_code_opt.as_ref().unwrap(), + db.node_by_key(&e).unwrap().inner.country_code_opt.as_ref().unwrap()); println!("f {} - g {} - h {} - i {} - j {}", - db.node_by_key(&f).unwrap().inner.country_code_opt.as_ref().unwrap(), - db.node_by_key(&g).unwrap().inner.country_code_opt.as_ref().unwrap(), - db.node_by_key(&h).unwrap().inner.country_code_opt.as_ref().unwrap(), - db.node_by_key(&i).unwrap().inner.country_code_opt.as_ref().unwrap(), - db.node_by_key(&j).unwrap().inner.country_code_opt.as_ref().unwrap() + db.node_by_key(&f).unwrap().inner.country_code_opt.as_ref().unwrap(), + db.node_by_key(&g).unwrap().inner.country_code_opt.as_ref().unwrap(), + db.node_by_key(&h).unwrap().inner.country_code_opt.as_ref().unwrap(), + db.node_by_key(&i).unwrap().inner.country_code_opt.as_ref().unwrap(), + db.node_by_key(&j).unwrap().inner.country_code_opt.as_ref().unwrap() ); println!("k {} - l {} - m {} - n {} - o {}", - db.node_by_key(&k).unwrap().inner.country_code_opt.as_ref().unwrap(), - db.node_by_key(&l).unwrap().inner.country_code_opt.as_ref().unwrap(), - db.node_by_key(&m).unwrap().inner.country_code_opt.as_ref().unwrap(), - db.node_by_key(&n).unwrap().inner.country_code_opt.as_ref().unwrap(), - db.node_by_key(&o).unwrap().inner.country_code_opt.as_ref().unwrap() + db.node_by_key(&k).unwrap().inner.country_code_opt.as_ref().unwrap(), + db.node_by_key(&l).unwrap().inner.country_code_opt.as_ref().unwrap(), + db.node_by_key(&m).unwrap().inner.country_code_opt.as_ref().unwrap(), + db.node_by_key(&n).unwrap().inner.country_code_opt.as_ref().unwrap(), + db.node_by_key(&o).unwrap().inner.country_code_opt.as_ref().unwrap() ); println!("p {} - q {} - r {} - s {} - t {}", - db.node_by_key(&p).unwrap().inner.country_code_opt.as_ref().unwrap(), - db.node_by_key(&q).unwrap().inner.country_code_opt.as_ref().unwrap(), - db.node_by_key(&r).unwrap().inner.country_code_opt.as_ref().unwrap(), - db.node_by_key(&s).unwrap().inner.country_code_opt.as_ref().unwrap(), - db.node_by_key(&t).unwrap().inner.country_code_opt.as_ref().unwrap() + db.node_by_key(&p).unwrap().inner.country_code_opt.as_ref().unwrap(), + db.node_by_key(&q).unwrap().inner.country_code_opt.as_ref().unwrap(), + db.node_by_key(&r).unwrap().inner.country_code_opt.as_ref().unwrap(), + db.node_by_key(&s).unwrap().inner.country_code_opt.as_ref().unwrap(), + db.node_by_key(&t).unwrap().inner.country_code_opt.as_ref().unwrap() ); println!("u {} - v {} - w {} - x {} - y {}", - db.node_by_key(&u).unwrap().inner.country_code_opt.as_ref().unwrap(), - db.node_by_key(&v).unwrap().inner.country_code_opt.as_ref().unwrap(), - db.node_by_key(&w).unwrap().inner.country_code_opt.as_ref().unwrap(), - db.node_by_key(&x).unwrap().inner.country_code_opt.as_ref().unwrap(), - db.node_by_key(&y).unwrap().inner.country_code_opt.as_ref().unwrap(), + db.node_by_key(&u).unwrap().inner.country_code_opt.as_ref().unwrap(), + db.node_by_key(&v).unwrap().inner.country_code_opt.as_ref().unwrap(), + db.node_by_key(&w).unwrap().inner.country_code_opt.as_ref().unwrap(), + db.node_by_key(&x).unwrap().inner.country_code_opt.as_ref().unwrap(), + db.node_by_key(&y).unwrap().inner.country_code_opt.as_ref().unwrap(), ); - - 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()); subject.handle_exit_location_message(message, 0, 0); let before = Instant::now(); @@ -4040,6 +4136,7 @@ mod tests { // let route = subject // .find_best_route_segment(&l, Some(&n), 3, 10000, RouteDirection::Back, None, None) // .unwrap(); + println!("exit countries {:?}", subject.user_exit_preferences.exit_countries); let route_cz = subject.find_best_route_segment( &l, None, @@ -4057,6 +4154,7 @@ mod tests { println!("key {:?}, country_code {:?}", &key, checkdb.node_by_key(&key).unwrap().inner.country_code_opt); }); let exit_node = checkdb.node_by_key(&route_cz.as_ref().unwrap().get(route_cz.as_ref().unwrap().len() - 1).unwrap()); + assert_eq!(exit_node.unwrap().public_key(), &c); assert_eq!(exit_node.unwrap().inner.country_code_opt, Some("CZ".to_string())); // assert_eq!(route, vec![&l, &g, &h, &i, &n]); // Cheaper than [&l, &q, &r, &s, &n] let interval = after.duration_since(before); @@ -4079,6 +4177,16 @@ mod tests { fn find_best_segment_traces_unreachable_country_code_exit_node() { init_test_logging(); let mut subject = make_standard_subject(); + let (recipient, _) = make_node_to_ui_recipient(); + subject.node_to_ui_recipient_opt = Some(recipient); + subject.user_exit_preferences.exit_location_preference = ExitPreference::ExitCountryWithFallback; + let message = UiSetExitLocationRequest { + fallback_routing: false, + exit_locations: vec![CountryCodes { + country_codes: vec!["CZ".to_string()], + priority: 1, + }], + }; 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(); @@ -4087,6 +4195,7 @@ mod tests { db.add_arbitrary_full_neighbor(p, c); db.add_arbitrary_full_neighbor(c, b); db.add_arbitrary_full_neighbor(c, a); + subject.handle_exit_location_message(message, 0, 0); let route_cz = subject.find_best_route_segment( p, @@ -4097,12 +4206,7 @@ mod tests { None, ); - TestLogHandler::new().exists_log_containing( - "Node with PubKey 0x05060708 is not from requested Country \"CZ\" during ExitRequest; Undesirability: 156816078 + 100000000 = 256816078", - ); - TestLogHandler::new().exists_log_containing( - "Node with PubKey 0x02030405 is not from requested Country \"CZ\" during ExitRequest; Undesirability: 123482745 + 100000000 = 223482745", - ); + assert_eq!(route_cz, None); } #[test] diff --git a/node/src/neighborhood/node_record.rs b/node/src/neighborhood/node_record.rs index 3cfc71f83..a84349e8f 100644 --- a/node/src/neighborhood/node_record.rs +++ b/node/src/neighborhood/node_record.rs @@ -113,10 +113,6 @@ impl NodeRecord { NodeDescriptor::from((self, chain, cryptde)) } - pub fn country_code_exeption(&self) -> u64 { - self.metadata.country_undesirability as u64 - } - pub fn set_node_addr( &mut self, node_addr: &NodeAddr,