Skip to content

Commit 44b58e4

Browse files
authored
Gh 599 - Neighborhood Graph command (#681)
* implementation of dotgraph in Neighborhood with test * using UiGetNeighborhoodGraphResponse for serde_json * masq ui client command implementation * formatting * factory for command and few more tests * example.com to www.example.com * addressed review comments * fixing the test assertion in can_deserialize_ui_get_neighborhood_graph
1 parent 0616ea9 commit 44b58e4

File tree

17 files changed

+245
-21
lines changed

17 files changed

+245
-21
lines changed

masq/src/command_factory.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::commands::descriptor_command::DescriptorCommand;
1111
use crate::commands::exit_location_command::SetExitLocationCommand;
1212
use crate::commands::financials_command::FinancialsCommand;
1313
use crate::commands::generate_wallets_command::GenerateWalletsCommand;
14+
use crate::commands::neighborhood_graph_command::GetNeighborhoodGraphCommand;
1415
use crate::commands::recover_wallets_command::RecoverWalletsCommand;
1516
use crate::commands::scan_command::ScanCommand;
1617
use crate::commands::set_configuration_command::SetConfigurationCommand;
@@ -65,6 +66,10 @@ impl CommandFactory for CommandFactoryReal {
6566
Ok(command) => Box::new(command),
6667
Err(msg) => return Err(CommandSyntax(msg)),
6768
},
69+
"neighborhood-graph" => match GetNeighborhoodGraphCommand::new(pieces) {
70+
Ok(command) => Box::new(command),
71+
Err(msg) => return Err(CommandSyntax(msg)),
72+
},
6873
"recover-wallets" => match RecoverWalletsCommand::new(pieces) {
6974
Ok(command) => Box::new(command),
7075
Err(msg) => return Err(CommandSyntax(msg)),
@@ -292,6 +297,21 @@ mod tests {
292297
);
293298
}
294299

300+
#[test]
301+
fn factory_produces_neighborhood_graph() {
302+
let subject = CommandFactoryReal::new();
303+
304+
let command = subject.make(&["neighborhood-graph".to_string()]).unwrap();
305+
306+
assert_eq!(
307+
command
308+
.as_any()
309+
.downcast_ref::<GetNeighborhoodGraphCommand>()
310+
.unwrap(),
311+
&GetNeighborhoodGraphCommand {}
312+
);
313+
}
314+
295315
#[test]
296316
fn complains_about_set_configuration_command_with_no_parameters() {
297317
let subject = CommandFactoryReal::new();

masq/src/commands/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,5 @@ pub mod setup_command;
1717
pub mod shutdown_command;
1818
pub mod start_command;
1919
pub mod wallet_addresses_command;
20+
21+
pub mod neighborhood_graph_command;
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use clap::{App, SubCommand};
2+
use masq_lib::{as_any_ref_in_trait_impl, short_writeln};
3+
4+
use crate::command_context::CommandContext;
5+
use crate::commands::commands_common::CommandError::Payload;
6+
use crate::commands::commands_common::{
7+
transaction, Command, CommandError, STANDARD_COMMAND_TIMEOUT_MILLIS,
8+
};
9+
use masq_lib::messages::{UiGetNeighborhoodGraphRequest, UiGetNeighborhoodGraphResponse};
10+
11+
const NEIGHBORHOOD_GRAPH_HELP: &str = "Use this command plainly, without any flags or arguments. The result will be delivered in digraph format, documentation at https://graphviz.org/documentation/";
12+
13+
pub fn get_neighborhood_graph_subcommand() -> App<'static, 'static> {
14+
SubCommand::with_name("neighborhood-graph").about(NEIGHBORHOOD_GRAPH_HELP)
15+
}
16+
17+
#[derive(Debug, PartialEq, Eq)]
18+
pub struct GetNeighborhoodGraphCommand {}
19+
20+
impl GetNeighborhoodGraphCommand {
21+
pub fn new(pieces: &[String]) -> Result<Self, String> {
22+
match get_neighborhood_graph_subcommand().get_matches_from_safe(pieces) {
23+
Ok(_) => Ok(GetNeighborhoodGraphCommand {}),
24+
Err(e) => Err(format!("GetNeighborhoodGraphCommand {}", e)),
25+
}
26+
}
27+
}
28+
29+
impl Command for GetNeighborhoodGraphCommand {
30+
fn execute(&self, context: &mut dyn CommandContext) -> Result<(), CommandError> {
31+
let input = UiGetNeighborhoodGraphRequest {};
32+
let output: Result<UiGetNeighborhoodGraphResponse, CommandError> =
33+
transaction(input, context, STANDARD_COMMAND_TIMEOUT_MILLIS);
34+
match output {
35+
Ok(neighborhood_graph) => {
36+
short_writeln!(
37+
context.stdout(),
38+
"Graph of the Node's neighborhood database: {}",
39+
neighborhood_graph.graph.as_str()
40+
);
41+
Ok(())
42+
}
43+
Err(Payload(code, message)) => {
44+
short_writeln!(context.stderr(), "code: {}\nmessage: {}", code, message);
45+
Err(Payload(code, message))
46+
}
47+
Err(err) => {
48+
short_writeln!(context.stderr(), "Error: {}", err);
49+
Err(err)
50+
}
51+
}
52+
}
53+
54+
as_any_ref_in_trait_impl!();
55+
}
56+
57+
#[cfg(test)]
58+
pub mod tests {
59+
use super::*;
60+
use crate::test_utils::mocks::CommandContextMock;
61+
use masq_lib::messages::ToMessageBody;
62+
use std::sync::{Arc, Mutex};
63+
64+
#[test]
65+
fn can_deserialize_ui_get_neighborhood_graph() {
66+
let transact_params_arc = Arc::new(Mutex::new(vec![]));
67+
let mut context = CommandContextMock::new()
68+
.transact_params(&transact_params_arc)
69+
.transact_result(Ok(UiGetNeighborhoodGraphResponse {
70+
graph: "digraph db { \"AQIDBA\" [label=\"AR v0 AU\\nAQIDBA\\n1.2.3.4:1234\"]; \"HZ5vwwJPhfUZVy85E76GZUUam9SMgyaw+QaZvAMuizo\" [label=\"AR v0 ZZ\\nHZ5vwwJP\\n9.9.9.9:9999\"] [style=filled]; \"AgMEBQ\" [label=\"AR v0 FR\\nAgMEBQ\\n2.3.4.5:2345\"]; \"AwQFBg\" [label=\"AR v0 CN\\nAwQFBg\\n3.4.5.6:3456\"]; \"BAUGBw\" [label=\"AR v0 US\\nBAUGBw\\n4.5.6.7:4567\"]; \"AQIDBA\" -> \"HZ5vwwJPhfUZVy85E76GZUUam9SMgyaw+QaZvAMuizo\"; \"AQIDBA\" -> \"AgMEBQ\"; \"HZ5vwwJPhfUZVy85E76GZUUam9SMgyaw+QaZvAMuizo\" -> \"AQIDBA\"; \"AgMEBQ\" -> \"AwQFBg\"; \"AgMEBQ\" -> \"AQIDBA\"; \"AwQFBg\" -> \"BAUGBw\"; \"AwQFBg\" -> \"AgMEBQ\"; \"BAUGBw\" -> \"AwQFBg\"; }".to_string()
71+
}.tmb(0)));
72+
let stderr_arc = context.stderr_arc();
73+
let stdout_arc = context.stdout_arc();
74+
let subject =
75+
GetNeighborhoodGraphCommand::new(&["neighborhood-graph".to_string()]).unwrap();
76+
77+
let result = subject.execute(&mut context);
78+
79+
assert_eq!(result, Ok(()));
80+
let expected_request = UiGetNeighborhoodGraphRequest {};
81+
let transact_params = transact_params_arc.lock().unwrap();
82+
let expected_message_body = expected_request.tmb(0);
83+
assert_eq!(
84+
transact_params.as_slice(),
85+
&[(expected_message_body, STANDARD_COMMAND_TIMEOUT_MILLIS)]
86+
);
87+
let stdout = stdout_arc.lock().unwrap();
88+
let graph_str = "Graph of the Node's neighborhood database: digraph db { \"AQIDBA\" [label=\"AR v0 AU\\nAQIDBA\\n1.2.3.4:1234\"]; \"HZ5vwwJPhfUZVy85E76GZUUam9SMgyaw+QaZvAMuizo\" [label=\"AR v0 ZZ\\nHZ5vwwJP\\n9.9.9.9:9999\"] [style=filled]; \"AgMEBQ\" [label=\"AR v0 FR\\nAgMEBQ\\n2.3.4.5:2345\"]; \"AwQFBg\" [label=\"AR v0 CN\\nAwQFBg\\n3.4.5.6:3456\"]; \"BAUGBw\" [label=\"AR v0 US\\nBAUGBw\\n4.5.6.7:4567\"]; \"AQIDBA\" -> \"HZ5vwwJPhfUZVy85E76GZUUam9SMgyaw+QaZvAMuizo\"; \"AQIDBA\" -> \"AgMEBQ\"; \"HZ5vwwJPhfUZVy85E76GZUUam9SMgyaw+QaZvAMuizo\" -> \"AQIDBA\"; \"AgMEBQ\" -> \"AwQFBg\"; \"AgMEBQ\" -> \"AQIDBA\"; \"AwQFBg\" -> \"BAUGBw\"; \"AwQFBg\" -> \"AgMEBQ\"; \"BAUGBw\" -> \"AwQFBg\"; }\n";
89+
assert_eq!(&stdout.get_string(), graph_str);
90+
let stderr = stderr_arc.lock().unwrap();
91+
assert_eq!(&stderr.get_string(), "");
92+
}
93+
}

masq/src/schema.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::commands::descriptor_command::descriptor_subcommand;
1111
use crate::commands::exit_location_command::exit_location_subcommand;
1212
use crate::commands::financials_command::args_validation::financials_subcommand;
1313
use crate::commands::generate_wallets_command::generate_wallets_subcommand;
14+
use crate::commands::neighborhood_graph_command::get_neighborhood_graph_subcommand;
1415
use crate::commands::recover_wallets_command::recover_wallets_subcommand;
1516
use crate::commands::scan_command::scan_subcommand;
1617
use crate::commands::set_configuration_command::set_configuration_subcommand;
@@ -71,6 +72,7 @@ pub fn app() -> App<'static, 'static> {
7172
.subcommand(exit_location_subcommand())
7273
.subcommand(financials_subcommand())
7374
.subcommand(generate_wallets_subcommand())
75+
.subcommand(get_neighborhood_graph_subcommand())
7476
.subcommand(recover_wallets_subcommand())
7577
.subcommand(scan_subcommand())
7678
.subcommand(set_configuration_subcommand())

masq_lib/src/messages.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,18 @@ pub struct UiSetExitLocationResponse {
911911
}
912912
conversation_message!(UiSetExitLocationResponse, "exitLocation");
913913

914+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
915+
pub struct UiGetNeighborhoodGraphRequest {}
916+
917+
conversation_message!(UiGetNeighborhoodGraphRequest, "neighborhoodGraph");
918+
919+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
920+
pub struct UiGetNeighborhoodGraphResponse {
921+
pub graph: String,
922+
}
923+
924+
conversation_message!(UiGetNeighborhoodGraphResponse, "neighborhoodGraph");
925+
914926
#[cfg(test)]
915927
mod tests {
916928
use super::*;

multinode_integration_tests/tests/bookkeeping_test.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ fn provided_and_consumed_services_are_recorded_in_databases() {
2929

3030
let mut client = originating_node.make_client(8080, STANDARD_CLIENT_TIMEOUT_MILLIS);
3131
client.set_timeout(Duration::from_secs(10));
32-
let request = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".as_bytes();
32+
let request = "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n".as_bytes();
3333

3434
client.send_chunk(request);
3535
let response = String::from_utf8(client.wait_for_chunk()).unwrap();
3636
assert!(
3737
response.contains("<h1>Example Domain</h1>"),
38-
"Not from example.com:\n{}",
38+
"Not from www.example.com:\n{}",
3939
response
4040
);
4141

multinode_integration_tests/tests/communication_failure_test.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ fn neighborhood_notified_of_newly_missing_node() {
103103

104104
//Establish a client on the originating Node and send some ill-fated traffic.
105105
let mut client = originating_node.make_client(8080, STANDARD_CLIENT_TIMEOUT_MILLIS);
106-
client.send_chunk("GET http://example.com HTTP/1.1\r\n\r\n".as_bytes());
106+
client.send_chunk("GET http://www.example.com HTTP/1.1\r\n\r\n".as_bytes());
107107

108108
// Now direct the witness Node to wait for Gossip about the disappeared Node.
109109
let (disappearance_gossip, _) = witness_node
@@ -405,7 +405,7 @@ fn dns_resolution_failure_no_longer_blacklists_exit_node_for_all_hosts() {
405405
),
406406
);
407407

408-
client.send_chunk("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".as_bytes());
408+
client.send_chunk("GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n".as_bytes());
409409
let cheapest_node = node_list.first().unwrap();
410410
let cheapest_node_expired_cores_package = cheapest_node
411411
.wait_for_specific_package(

multinode_integration_tests/tests/data_routing_test.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ fn tls_end_to_end_routing_test() {
175175
.expect("Could not set read timeout to 1000ms");
176176
let connector = TlsConnector::new().expect("Could not build TlsConnector");
177177
match connector.connect(
178-
"example.com",
178+
"www.example.com",
179179
stream.try_clone().expect("Couldn't clone TcpStream"),
180180
) {
181181
Ok(s) => {
@@ -199,7 +199,7 @@ fn tls_end_to_end_routing_test() {
199199

200200
tls_stream.expect("Couldn't handshake")
201201
};
202-
let request = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".as_bytes();
202+
let request = "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n".as_bytes();
203203
tls_stream
204204
.write(request.clone())
205205
.expect("Could not write request to TLS stream");

node/src/blockchain/blockchain_bridge.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -662,7 +662,7 @@ mod tests {
662662
#[test]
663663
fn blockchain_interface_is_constructed_with_a_blockchain_service_url() {
664664
init_test_logging();
665-
let blockchain_service_url = "https://example.com";
665+
let blockchain_service_url = "https://www.example.com";
666666
let subject = BlockchainBridge::initialize_blockchain_interface(
667667
Some(blockchain_service_url.to_string()),
668668
TEST_DEFAULT_CHAIN,

node/src/neighborhood/mod.rs

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ use masq_lib::exit_locations::ExitLocationSet;
6666
use masq_lib::logger::Logger;
6767
use masq_lib::messages::{
6868
ExitLocation, FromMessageBody, ToMessageBody, UiConnectionStage, UiConnectionStatusRequest,
69-
UiSetExitLocationRequest, UiSetExitLocationResponse,
69+
UiGetNeighborhoodGraphRequest, UiGetNeighborhoodGraphResponse, UiSetExitLocationRequest,
70+
UiSetExitLocationResponse,
7071
};
7172
use masq_lib::messages::{UiConnectionStatusResponse, UiShutdownRequest};
7273
use masq_lib::ui_gateway::MessagePath::Conversation;
@@ -366,6 +367,8 @@ impl Handler<NodeFromUiMessage> for Neighborhood {
366367
self.handle_connection_status_message(client_id, context_id);
367368
} else if let Ok((body, _)) = UiShutdownRequest::fmb(msg.body.clone()) {
368369
self.handle_shutdown_order(client_id, body);
370+
} else if let Ok((_, context_id)) = UiGetNeighborhoodGraphRequest::fmb(msg.body.clone()) {
371+
self.handle_neighborhood_graph_message(client_id, context_id);
369372
} else {
370373
handle_ui_crash_request(msg, &self.logger, self.crashable, CRASH_KEY)
371374
}
@@ -1611,6 +1614,19 @@ impl Neighborhood {
16111614
undesirability + node_undesirability
16121615
}
16131616

1617+
fn handle_neighborhood_graph_message(&self, client_id: u64, context_id: u64) {
1618+
let graph = self.neighborhood_database.to_dot_graph();
1619+
let message = NodeToUiMessage {
1620+
target: MessageTarget::ClientId(client_id),
1621+
body: UiGetNeighborhoodGraphResponse { graph }.tmb(context_id),
1622+
};
1623+
self.node_to_ui_recipient_opt
1624+
.as_ref()
1625+
.expect("UI Gateway is unbound")
1626+
.try_send(message)
1627+
.expect("UiGateway is dead");
1628+
}
1629+
16141630
fn handle_exit_location_message(
16151631
&mut self,
16161632
message: UiSetExitLocationRequest,
@@ -3634,6 +3650,81 @@ mod tests {
36343650
assert_eq!(juicy_parts(result_1), (1, 1));
36353651
}
36363652

3653+
#[test]
3654+
fn handle_neighborhood_graph_message_works() {
3655+
let test_name = "handle_neighborhood_graph_message_works";
3656+
let system = System::new(test_name);
3657+
let (ui_gateway, _recorder, arc_recorder) = make_recorder();
3658+
let mut subject = make_standard_subject();
3659+
let root_node_ch = subject.neighborhood_database.root().clone();
3660+
let neighbor_one_au = make_node_record_cc(1234, true, "AU");
3661+
let neighbor_two_fr = make_node_record_cc(2345, true, "FR");
3662+
let neighbor_three_cn = make_node_record_cc(3456, true, "CN");
3663+
let neighbor_four_us = make_node_record_cc(4567, true, "US");
3664+
let root_pubkey = format!("{}", root_node_ch.public_key());
3665+
let neighbor_one_pubkey = format!("{}", neighbor_one_au.public_key());
3666+
let neighbor_two_pubkey = format!("{}", neighbor_two_fr.public_key());
3667+
let neighbor_three_pubkey = format!("{}", neighbor_three_cn.public_key());
3668+
let neighbor_four_pubkey = format!("{}", neighbor_four_us.public_key());
3669+
subject
3670+
.neighborhood_database
3671+
.add_node(neighbor_one_au.clone())
3672+
.unwrap();
3673+
subject
3674+
.neighborhood_database
3675+
.add_node(neighbor_two_fr.clone())
3676+
.unwrap();
3677+
subject
3678+
.neighborhood_database
3679+
.add_node(neighbor_three_cn.clone())
3680+
.unwrap();
3681+
subject
3682+
.neighborhood_database
3683+
.add_node(neighbor_four_us.clone())
3684+
.unwrap();
3685+
subject
3686+
.neighborhood_database
3687+
.add_arbitrary_full_neighbor(root_node_ch.public_key(), neighbor_one_au.public_key());
3688+
subject.neighborhood_database.add_arbitrary_full_neighbor(
3689+
neighbor_one_au.public_key(),
3690+
neighbor_two_fr.public_key(),
3691+
);
3692+
subject.neighborhood_database.add_arbitrary_full_neighbor(
3693+
neighbor_two_fr.public_key(),
3694+
neighbor_three_cn.public_key(),
3695+
);
3696+
subject.neighborhood_database.add_arbitrary_full_neighbor(
3697+
neighbor_three_cn.public_key(),
3698+
neighbor_four_us.public_key(),
3699+
);
3700+
let request = UiGetNeighborhoodGraphRequest {};
3701+
let message = NodeFromUiMessage {
3702+
client_id: 456,
3703+
body: request.tmb(465),
3704+
};
3705+
let subject_addr = subject.start();
3706+
let peer_actors = peer_actors_builder().ui_gateway(ui_gateway).build();
3707+
subject_addr.try_send(BindMessage { peer_actors }).unwrap();
3708+
3709+
subject_addr.try_send(message).unwrap();
3710+
System::current().stop();
3711+
system.run();
3712+
3713+
let recorder_result = arc_recorder.lock().unwrap();
3714+
let result = recorder_result
3715+
.get_record::<NodeToUiMessage>(0)
3716+
.body
3717+
.clone()
3718+
.payload
3719+
.unwrap();
3720+
let result_object: UiGetNeighborhoodGraphResponse = serde_json::from_str(&result).unwrap();
3721+
assert!(result_object.graph.contains(&root_pubkey));
3722+
assert!(result_object.graph.contains(&neighbor_one_pubkey));
3723+
assert!(result_object.graph.contains(&neighbor_two_pubkey));
3724+
assert!(result_object.graph.contains(&neighbor_three_pubkey));
3725+
assert!(result_object.graph.contains(&neighbor_four_pubkey));
3726+
}
3727+
36373728
#[test]
36383729
fn min_hops_change_affects_db_countries_and_exit_location_settings() {
36393730
let mut subject = make_standard_subject();

0 commit comments

Comments
 (0)