p2pool: implement async node ping in GUI

This commit is contained in:
hinto-janaiyo 2022-11-18 14:25:13 -05:00
parent 8a31a707d9
commit 1b96d3ba02
No known key found for this signature in database
GPG key ID: B1C5A64B80691E45
6 changed files with 133 additions and 153 deletions

24
CHANGELOG.md Normal file
View file

@ -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

View file

@ -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|| {

View file

@ -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<Mutex<Ping>>, og: Arc<Mutex<State>>) -> Result<Vec<NodeData>, anyhow::Error> {
pub async fn ping(ping: Arc<Mutex<Ping>>, og: Arc<Mutex<State>>) -> 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<Mutex<Ping>>, og: Arc<Mutex<State>>) -> Result<Vec<
// Random User Agent
let rand_user_agent = crate::Pkg::get_user_agent();
// Handle vector
// let mut handles: Vec<tokio::task::JoinHandle<Result<NodeData, anyhow::Error>>> = 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<Result<NodeData, anyhow::Error>> = 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<hyper::client::HttpConnector>, request: hyper::Request<hyper::Body>, ip: &'static str) -> Result<NodeData, anyhow::Error> {
async fn response(client: hyper::client::Client<hyper::client::HttpConnector>, request: hyper::Request<hyper::Body>, ip: &'static str, ping: Arc<Mutex<Ping>>, percent: f32, node_vec: Arc<Mutex<Vec<NodeData>>>) {
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<Mutex<Ping>>, og: Arc<Mutex<State>>) {
// 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, });
}

View file

@ -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;
});
}});

View file

@ -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
}

38
utils/prepare.sh Executable file
View file

@ -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"