From 1b96d3ba023f96ca186d5a65ab005fc1695ebb40 Mon Sep 17 00:00:00 2001 From: hinto-janaiyo Date: Fri, 18 Nov 2022 14:25:13 -0500 Subject: [PATCH] p2pool: implement async node ping in GUI --- CHANGELOG.md | 24 ++++++ src/gupax.rs | 2 +- src/node.rs | 207 +++++++++++++---------------------------------- src/p2pool.rs | 13 ++- src/update.rs | 2 +- utils/prepare.sh | 38 +++++++++ 6 files changed, 133 insertions(+), 153 deletions(-) create mode 100644 CHANGELOG.md create mode 100755 utils/prepare.sh diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..bae8bf7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,24 @@ +## v0.1.0 +## Prototype Release +* Added package updater (by default, via Tor using [`Arti`](https://blog.torproject.org/arti_100_released/)) +* Added [custom icons per OS](https://github.com/hinto-janaiyo/gupax/tree/main/images/icons) (File Explorer, Taskbar, Finder, App header, etc) +* Added Monero node [`JSON-RPC ping`](https://github.com/hinto-janaiyo/gupax/blob/main/src/node.rs) system, not yet in GUI +* Added `F11` fullscreen toggle +* Implemented `Ask before quit` +* Implemented `Auto-save` +* Binaries for all platforms (Windows, macOS, Linux) +* Added state file to save settings: + - Windows: `C:\Users\USER\AppData\Roaming\Gupax\gupax.toml` + - macOS: `/Users/USER/Library/Application Support/Gupax/gupax.toml` + - Linux: `/home/USER/.local/share/gupax/gupax.toml` + + +--- + + +## v0.0.1 +## Prototype Release +* Functional (slightly buggy) GUI external +* Elements have state (buttons, sliders, etc) +* No internals, no connections, no processes +* Only binaries for x86_64 Windows/Linux for now diff --git a/src/gupax.rs b/src/gupax.rs index 266c86f..afdd2ad 100644 --- a/src/gupax.rs +++ b/src/gupax.rs @@ -187,7 +187,7 @@ impl Gupax { } ui.spacing_mut().text_edit_width = ui.available_width() - SPACE; ui.set_enabled(!file_window.lock().unwrap().thread); - if ui.button("Open ").on_hover_text(GUPAX_SELECT).clicked() { + if ui.button("Open").on_hover_text(GUPAX_SELECT).clicked() { file_window.lock().unwrap().thread = true; let file_window = Arc::clone(file_window); thread::spawn(move|| { diff --git a/src/node.rs b/src/node.rs index 18ee5ab..ffdf942 100644 --- a/src/node.rs +++ b/src/node.rs @@ -69,7 +69,7 @@ impl std::fmt::Display for NodeEnum { } //---------------------------------------------------------------------------------------------------- Node data -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct NodeData { pub id: NodeEnum, pub ip: &'static str, @@ -119,6 +119,7 @@ impl Ping { } //---------------------------------------------------------------------------------------------------- IP <-> Enum functions +use crate::NodeEnum::*; // Function for returning IP/Enum pub fn ip_to_enum(ip: &'static str) -> NodeEnum { match ip { @@ -193,11 +194,29 @@ pub fn format_enum(id: NodeEnum) -> String { } //---------------------------------------------------------------------------------------------------- Main Ping function +// This is for pinging the community nodes to +// find the fastest/slowest one for the user. +// The process: +// - Send [get_info] JSON-RPC request over HTTP to all IPs +// - Measure each request in milliseconds +// - Timeout on requests over 5 seconds +// - Add data to appropriate struct +// - Sorting fastest to lowest is automatic (fastest nodes return ... the fastest) +// +// This used to be done 3x linearly but after testing, sending a single +// JSON-RPC call to all IPs asynchronously resulted in the same data. +// +// <300ms = GREEN +// <1000ms = YELLOW +// >1000ms = RED +// timeout = BLACK +// default = GRAY #[tokio::main] -pub async fn start(ping: Arc>, og: Arc>) -> Result, anyhow::Error> { +pub async fn ping(ping: Arc>, og: Arc>) -> Result<(), anyhow::Error> { // Start ping ping.lock().unwrap().pinging = true; ping.lock().unwrap().prog = 0.0; + let percent = (100.0 / ((NODE_IPS.len()) as f32)).floor(); // Create HTTP client let info = format!("{}", "Creating HTTP Client"); @@ -208,175 +227,65 @@ pub async fn start(ping: Arc>, og: Arc>) -> Result>> = vec![]; let mut handles = vec![]; + let node_vec = Arc::new(Mutex::new(Vec::new())); for ip in NODE_IPS { let client = client.clone(); + let ping = Arc::clone(&ping); + let node_vec = Arc::clone(&node_vec); let request = hyper::Request::builder() .method("POST") .uri("http://".to_string() + ip + "/json_rpc") .header("User-Agent", rand_user_agent) .body(hyper::Body::from(r#"{"jsonrpc":"2.0","id":"0","method":"get_info"}"#)) .unwrap(); - let handle = tokio::spawn(async move { response(client, request, ip).await }); -// let handle: tokio::task::JoinHandle> = tokio::spawn(async move { response(client, request, ip).await }); + let handle = tokio::spawn(async move { response(client, request, ip, ping, percent, node_vec).await }); handles.push(handle); } - let mut node_vec = vec![]; for handle in handles { - match handle.await { - Ok(data) => match data { Ok(data) => node_vec.push(data), _ => return Err(anyhow::Error::msg("fail")) }, - _ => return Err(anyhow::Error::msg("fail")), - }; + handle.await; } + let node_vec = node_vec.lock().unwrap().clone(); - Ok(node_vec) + let info = format!("Fastest node: {}ms ... {} @ {}", node_vec[0].ms, node_vec[0].id, node_vec[0].ip); + info!("Ping | {}", info); + let mut ping = ping.lock().unwrap(); + ping.fastest = node_vec[0].id; + ping.nodes = node_vec; + ping.prog = 100.0; + ping.msg = info; + ping.pinging = false; + ping.pinged = true; + ping.auto_selected = false; + drop(ping); + Ok(()) } -async fn response(client: hyper::client::Client, request: hyper::Request, ip: &'static str) -> Result { +async fn response(client: hyper::client::Client, request: hyper::Request, ip: &'static str, ping: Arc>, percent: f32, node_vec: Arc>>) { + let id = ip_to_enum(ip); let now = Instant::now(); - let response = tokio::time::timeout(Duration::from_secs(5), client.request(request)).await?; - let ms = now.elapsed().as_millis(); - let mut color = Color32::BLACK; + let ms; + match tokio::time::timeout(Duration::from_secs(5), client.request(request)).await { + Ok(_) => ms = now.elapsed().as_millis(), + Err(e) => { warn!("Ping | {}: {} ... FAIL ... {}", id, ip, e); ms = 5000; }, + }; + let color; if ms < 300 { - // GREEN - color = Color32::from_rgb(100, 230, 100); + color = Color32::from_rgb(100, 230, 100); // GREEN } else if ms < 1000 { - // YELLOW - color = Color32::from_rgb(230, 230, 100); + color = Color32::from_rgb(230, 230, 100); // YELLOW } else if ms < 5000 { - // RED - color = Color32::from_rgb(230, 50, 50); + color = Color32::from_rgb(230, 50, 50); // RED + } else { + color = Color32::BLACK; } - Ok(NodeData { - id: ip_to_enum(ip), - ip, - ms, - color, - }) -} - - -// This is for pinging the community nodes to -// find the fastest/slowest one for the user. -// The process: -// - Send 3 [get_info] JSON-RPC requests over HTTP -// - Measure each request in milliseconds as [u128] -// - Timeout on requests over 5 seconds -// - Calculate average time -// - Add data to appropriate struct -// - Sort fastest to lowest -// - Return [PingResult] (data and fastest node) -// -// This is done linearly since per IP since -// multi-threading might affect performance. -// -// <300ms = GREEN -// <1000ms = YELLOW -// >1000ms = RED -// timeout = BLACK -// default = GRAY -use crate::NodeEnum::*; -pub fn ping(ping: Arc>, og: Arc>) { - // Start ping - ping.lock().unwrap().pinging = true; - ping.lock().unwrap().prog = 0.0; - let info = format!("{}", "Creating HTTPS Client"); + let info = format!("{}ms ... {}: {}", ms, id, ip); info!("Ping | {}", info); - ping.lock().unwrap().msg = info; - let percent = (100 / (NODE_IPS.len() - 1)) as f32 / 3.0; - - // Create Node vector - let mut nodes = Vec::new(); - - // Create JSON request - let mut get_info = HashMap::new(); - get_info.insert("jsonrpc", "2.0"); - get_info.insert("id", "0"); - get_info.insert("method", "get_info"); - - // Misc Settings - let mut vec: Vec<(NodeEnum, u128)> = Vec::new(); - - // Create HTTP Client - let timeout_sec = Duration::from_millis(5000); - let client = ClientBuilder::new(); - let client = ClientBuilder::timeout(client, timeout_sec); - let client = ClientBuilder::build(client).unwrap(); - - for ip in NODE_IPS.iter() { - // Match IP - let id = ip_to_enum(ip); - // Misc - let mut timeout = 0; - let mut mid = Duration::new(0, 0); - - // Start JSON-RPC request - for i in 1..=3 { - ping.lock().unwrap().msg = format!("{}: {} [{}/3]", id, ip, i); - let now = Instant::now(); - let http = "http://".to_string() + &**ip + "/json_rpc"; - match client.post(http).json(&get_info).send() { - Ok(_) => mid += now.elapsed(), - Err(_) => { - mid += timeout_sec; - timeout += 1; - let error = format!("Timeout [{}/3] ... {:#?} ... {}", timeout, id, ip); - error!("Ping | {}", error); - ping.lock().unwrap().msg = error; - }, - }; - ping.lock().unwrap().prog += percent; - } - - // Calculate average - let ms = mid.as_millis() / 3; - vec.push((id, ms)); - let info = format!("{}ms ... {}: {}", ms, id, ip); - info!("Ping | {}", info); - ping.lock().unwrap().msg = format!("{}", info); - let color: Color32; - if timeout == 3 { - color = Color32::BLACK; - } else if ms >= 1000 { - // RED - color = Color32::from_rgb(230, 50, 50); - } else if ms >= 300 { - // YELLOW - color = Color32::from_rgb(230, 230, 100); - } else { - // GREEN - color = Color32::from_rgb(100, 230, 100); - } - nodes.push(NodeData { id, ip, ms, color }) - } - - let percent = (100.0 - ping.lock().unwrap().prog) / 2.0; - ping.lock().unwrap().prog += percent; - ping.lock().unwrap().msg = "Calculating fastest node".to_string(); - // Calculate fastest out of all nodes - let mut fastest: NodeEnum = vec[0].0; - let mut best_ms: u128 = vec[0].1; - for (id, ms) in vec.iter() { - if ms < &best_ms { - fastest = *id; - best_ms = *ms; - } - } - let info = format!("Fastest node: {}ms ... {} @ {}", best_ms, fastest, enum_to_ip(fastest)); - info!("Ping | {}", info); - let mut guard = ping.lock().unwrap(); - guard.nodes = nodes; - guard.fastest = fastest; - guard.prog = 100.0; - guard.msg = info; - guard.pinging = false; - guard.pinged = true; - drop(guard); - if og.lock().unwrap().p2pool.auto_select { - ping.lock().unwrap().auto_selected = false; - } - info!("Ping ... OK"); + let mut ping = ping.lock().unwrap(); + ping.msg = info; + ping.prog += percent; + drop(ping); + node_vec.lock().unwrap().push(NodeData { id: ip_to_enum(ip), ip, ms, color, }); } diff --git a/src/p2pool.rs b/src/p2pool.rs index 3fdeba2..d31ebbe 100644 --- a/src/p2pool.rs +++ b/src/p2pool.rs @@ -141,11 +141,20 @@ impl P2pool { // [Ping Button] ui.set_enabled(!ping.lock().unwrap().pinging); if ui.add_sized([width, height], Button::new("Ping community nodes")).on_hover_text(P2POOL_PING).clicked() { - let ping = Arc::clone(&ping); + let ping1 = Arc::clone(&ping); + let ping2 = Arc::clone(&ping); let og = Arc::clone(og); thread::spawn(move|| { info!("Spawning ping thread..."); - crate::node::ping(ping, og); + match crate::node::ping(ping1, og) { + Ok(_) => info!("Ping ... OK"), + Err(err) => { + error!("Ping ... FAIL ... {}", err); + ping2.lock().unwrap().pinged = false; + ping2.lock().unwrap().msg = err.to_string(); + }, + }; + ping2.lock().unwrap().pinging = false; }); }}); diff --git a/src/update.rs b/src/update.rs index 550bbdd..05da782 100644 --- a/src/update.rs +++ b/src/update.rs @@ -676,7 +676,7 @@ impl Pkg { pub fn get_user_agent() -> &'static str { let rand = thread_rng().gen_range(0..50); let user_agent = FAKE_USER_AGENT[rand]; - info!("Update | Randomly selecting User-Agent ({}/50) ... {}", rand, user_agent); + info!("Randomly selected User-Agent ({}/50) ... {}", rand, user_agent); user_agent } diff --git a/utils/prepare.sh b/utils/prepare.sh new file mode 100755 index 0000000..5bea494 --- /dev/null +++ b/utils/prepare.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +# prepare new [gupax] version in: +# 1. README.md +# 2. CHANGELOG.md +# 3. Cargo.toml + +# $1 = new_version +set -ex +sudo -v +[[ $1 = v* ]] +[[ $PWD = */gupax ]] + +# get old GUPAX_VER +OLD_VER="v$(grep -m1 "version" Cargo.toml | grep -o "[0-9].[0-9].[0-9]")" + +# sed change +sed -i "s/$OLD_VER/$1/g" README.md +sed -i "s/$OLD_VER/$1/" Cargo.toml + +# changelog +cat << EOM > CHANGELOG.md.new +# $1 +## Updates +* + +## Fixes +* + +--- + +EOM +cat CHANGELOG.md >> CHANGELOG.md.new +mv -f CHANGELOG.md.new CHANGELOG.md + +# commit +git add . +git commit -m "prepare $1"