gupaxx/src/node.rs

447 lines
14 KiB
Rust
Raw Normal View History

// Gupax - GUI Uniting P2Pool And XMRig
//
// Copyright (c) 2022 hinto-janaiyo
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
constants::*,
macros::*,
};
use serde::{Serialize,Deserialize};
use rand::{thread_rng, Rng};
use std::time::{Instant,Duration};
use std::sync::{Arc,Mutex};
use egui::Color32;
use log::*;
2022-11-21 01:21:47 +00:00
use hyper::{
client::HttpConnector,
Client,Body,Request,
};
2022-11-18 03:45:57 +00:00
//---------------------------------------------------------------------------------------------------- Node list
// Remote Monero Nodes with ZMQ enabled, sourced from: [https://github.com/hinto-janaiyo/monero-nodes]
// The format is an array of tuples consisting of: (ARRAY_INDEX, IP, LOCATION, RPC_PORT, ZMQ_PORT)
pub const REMOTE_NODES: [(usize, &str, &str, &str, &str); 22] = [
(0, "monero.10z.com.ar", "AR - Buenos Aires F.D.", "18089", "18084"),
(1, "escom.sadovo.com", "BG - Plovdiv", "18089", "18084"),
(2, "monero2.10z.com.ar", "BR - São Paulo", "18089", "18083"),
(3, "monero1.heitechsoft.com", "CA - Ontario", "18081", "18084"),
(4, "node.monerodevs.org", "CA - Quebec", "18089", "18084"),
(5, "de.poiuty.com", "DE - Berlin", "18081", "18084"),
(6, "m1.poiuty.com", "DE - Berlin", "18081", "18084"),
(7, "p2pmd.xmrvsbeast.com", "DE - Hesse", "18081", "18083"),
(8, "fbx.tranbert.com", "FR - Île-de-France", "18089", "18084"),
(9, "reynald.ro", "FR - Île-de-France", "18089", "18084"),
(10, "node2.monerodevs.org", "FR - Occitanie", "18089", "18084"),
(11, "monero.homeqloud.com", "GR - East Macedonia and Thrace", "18089", "18083"),
(12, "home.allantaylor.kiwi", "NZ - Canterbury", "18089", "18083"),
(13, "ru.poiuty.com", "RU - Kuzbass", "18081", "18084"),
(14, "radishfields.hopto.org", "US - Colorado", "18081", "18084"),
(15, "xmrbandwagon.hopto.org", "US - Colorado", "18081", "18084"),
(16, "xmr.spotlightsound.com", "US - Kansas", "18081", "18084"),
(17, "xmrnode.facspro.net", "US - Nebraska", "18089", "18084"),
(18, "jameswillhoite.com", "US - Ohio", "18089", "18084"),
(19, "moneronode.ddns.net", "US - Pennsylvania", "18089", "18084"),
(20, "node.richfowler.net", "US - Pennsylvania", "18089", "18084"),
(21, "bunkernet.ddns.net", "ZA - Western Cape", "18089", "18084"),
2022-10-16 21:29:24 +00:00
];
pub const REMOTE_NODE_LENGTH: usize = REMOTE_NODES.len();
pub const REMOTE_NODE_MAX_CHARS: usize = 24; // monero1.heitechsoft.com
pub struct RemoteNode {
pub index: usize,
pub ip: &'static str,
pub location: &'static str,
pub rpc: &'static str,
pub zmq: &'static str,
}
impl Default for RemoteNode {
fn default() -> Self {
Self::new()
}
}
impl RemoteNode {
pub fn new() -> Self {
let (index, ip, location, rpc, zmq) = REMOTE_NODES[0];
Self {
index,
ip,
location,
rpc,
zmq,
}
}
pub fn check_exists(og_ip: &str) -> String {
for (_, ip, _, _, _) in REMOTE_NODES {
if og_ip == ip {
info!("Found remote node in array: {}", ip);
return ip.to_string()
}
}
let ip = REMOTE_NODES[0].1.to_string();
warn!("[{}] remote node does not exist, returning default: {}", og_ip, ip);
ip
}
// Returns a default if IP is not found.
pub fn from_ip(from_ip: &str) -> Self {
for (index, ip, location, rpc, zmq) in REMOTE_NODES {
if from_ip == ip {
return Self { index, ip, location, rpc, zmq }
}
}
Self::new()
}
// Returns a default if index is not found in the const array.
pub fn from_index(index: usize) -> Self {
if index > REMOTE_NODE_LENGTH {
Self::new()
} else {
let (index, ip, location, rpc, zmq) = REMOTE_NODES[index];
Self { index, ip, location, rpc, zmq }
}
}
pub fn from_tuple(t: (usize, &'static str, &'static str, &'static str, &'static str)) -> Self {
let (index, ip, location, rpc, zmq) = (t.0, t.1, t.2, t.3, t.4);
Self { index, ip, location, rpc, zmq }
}
pub fn get_ip_rpc_zmq(og_ip: &str) -> (&str, &str, &str) {
for (_, ip, _, rpc, zmq) in REMOTE_NODES {
if og_ip == ip { return (ip, rpc, zmq) }
}
let (_, ip, _, rpc, zmq) = REMOTE_NODES[0];
(ip, rpc, zmq)
}
// Return a random node (that isn't the one already selected).
pub fn get_random(current_ip: &str) -> String {
let mut rng = thread_rng().gen_range(0..REMOTE_NODE_LENGTH);
let mut node = REMOTE_NODES[rng].1;
while current_ip == node {
rng = thread_rng().gen_range(0..REMOTE_NODE_LENGTH);
node = REMOTE_NODES[rng].1;
}
node.to_string()
}
// Return the node [-1] of this one
pub fn get_last(current_ip: &str) -> String {
let mut found = false;
let mut last = current_ip;
for (_, ip, _, _, _) in REMOTE_NODES {
if found { return ip.to_string() }
if current_ip == ip { found = true; } else { last = ip; }
}
last.to_string()
}
// Return the node [+1] of this one
pub fn get_next(current_ip: &str) -> String {
let mut found = false;
for (_, ip, _, _, _) in REMOTE_NODES {
if found { return ip.to_string() }
if current_ip == ip { found = true; }
}
current_ip.to_string()
}
// This returns relative to the ping.
pub fn get_last_from_ping(current_ip: &str, nodes: &Vec<NodeData>) -> String {
let mut found = false;
let mut last = current_ip;
for data in nodes {
if found { return last.to_string() }
if current_ip == data.ip { found = true; } else { last = data.ip; }
}
last.to_string()
}
pub fn get_next_from_ping(current_ip: &str, nodes: &Vec<NodeData>) -> String {
let mut found = false;
for data in nodes {
if found { return data.ip.to_string() }
if current_ip == data.ip { found = true; }
}
current_ip.to_string()
}
}
impl std::fmt::Display for RemoteNode {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:#?}", self.ip)
}
}
//---------------------------------------------------------------------------------------------------- Formatting
// 5000 = 4 max length
pub fn format_ms(ms: u128) -> String {
match ms.to_string().len() {
1 => format!("{}ms ", ms),
2 => format!("{}ms ", ms),
3 => format!("{}ms ", ms),
_ => format!("{}ms", ms),
}
}
// format_ip_location(monero1.heitechsoft.com) -> "monero1.heitechsoft.com | XX - LOCATION"
// [extra_space] controls whether extra space is appended so the list aligns.
pub fn format_ip_location(og_ip: &str, extra_space: bool) -> String {
for (_, ip, location, _, _) in REMOTE_NODES {
if og_ip == ip {
let ip = if extra_space { format_ip(ip) } else { ip.to_string() };
return format!("{} | {}", ip, location)
}
}
"??? | ???".to_string()
}
// monero1.heitechsoft.com = 24 max length
pub fn format_ip(ip: &str) -> String {
match ip.len() {
1 => format!("{} ", ip),
2 => format!("{} ", ip),
3 => format!("{} ", ip),
4 => format!("{} ", ip),
5 => format!("{} ", ip),
6 => format!("{} ", ip),
7 => format!("{} ", ip),
8 => format!("{} ", ip),
9 => format!("{} ", ip),
10 => format!("{} ", ip),
11 => format!("{} ", ip),
12 => format!("{} ", ip),
13 => format!("{} ", ip),
14 => format!("{} ", ip),
15 => format!("{} ", ip),
16 => format!("{} ", ip),
17 => format!("{} ", ip),
18 => format!("{} ", ip),
19 => format!("{} ", ip),
20 => format!("{} ", ip),
21 => format!("{} ", ip),
22 => format!("{} ", ip),
_ => format!("{}", ip),
}
}
//---------------------------------------------------------------------------------------------------- Node data
#[derive(Debug, Clone)]
pub struct NodeData {
2022-10-16 21:29:24 +00:00
pub ip: &'static str,
pub ms: u128,
pub color: Color32,
}
impl NodeData {
pub fn new_vec() -> Vec<Self> {
let mut vec = Vec::new();
for (_, ip, _, _, _) in REMOTE_NODES {
vec.push(Self {
ip,
ms: 0,
color: Color32::LIGHT_GRAY,
});
}
vec
}
}
//---------------------------------------------------------------------------------------------------- Ping data
#[derive(Debug)]
pub struct Ping {
pub nodes: Vec<NodeData>,
pub fastest: &'static str,
pub pinging: bool,
pub msg: String,
pub prog: f32,
pub pinged: bool,
pub auto_selected: bool,
}
2022-11-18 03:45:57 +00:00
2022-12-14 03:41:05 +00:00
impl Default for Ping {
fn default() -> Self {
Self::new()
}
}
impl Ping {
pub fn new() -> Self {
Self {
nodes: NodeData::new_vec(),
fastest: REMOTE_NODES[0].1,
pinging: false,
msg: "No ping in progress".to_string(),
prog: 0.0,
pinged: false,
auto_selected: true,
}
}
2022-11-18 03:45:57 +00:00
//---------------------------------------------------------------------------------------------------- Main Ping function
2022-11-21 01:21:47 +00:00
// Intermediate function for spawning thread
pub fn spawn_thread(ping: &Arc<Mutex<Self>>) {
info!("Spawning ping thread...");
2022-11-24 04:03:56 +00:00
let ping = Arc::clone(ping);
std::thread::spawn(move|| {
let now = Instant::now();
match Self::ping(&ping) {
Ok(msg) => {
2022-11-22 01:10:47 +00:00
info!("Ping ... OK");
lock!(ping).msg = msg;
lock!(ping).pinged = true;
lock!(ping).auto_selected = false;
lock!(ping).prog = 100.0;
2022-11-22 01:10:47 +00:00
},
Err(err) => {
error!("Ping ... FAIL ... {}", err);
lock!(ping).pinged = false;
lock!(ping).msg = err.to_string();
},
}
info!("Ping ... Took [{}] seconds...", now.elapsed().as_secs_f32());
lock!(ping).pinging = false;
});
2022-11-18 03:45:57 +00:00
}
// This is for pinging the remote 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
// >300ms = YELLOW
// >500ms = RED
// timeout = BLACK
// default = GRAY
#[tokio::main]
pub async fn ping(ping: &Arc<Mutex<Self>>) -> Result<String, anyhow::Error> {
// Start ping
2022-12-14 03:41:05 +00:00
let ping = Arc::clone(ping);
lock!(ping).pinging = true;
lock!(ping).prog = 0.0;
let percent = (100.0 / (REMOTE_NODE_LENGTH as f32)).floor();
// Create HTTP client
2022-11-24 04:03:56 +00:00
let info = "Creating HTTP Client".to_string();
lock!(ping).msg = info;
2022-11-21 01:21:47 +00:00
let client: Client<HttpConnector> = Client::builder()
.build(HttpConnector::new());
// Random User Agent
let rand_user_agent = crate::Pkg::get_user_agent();
// Handle vector
let mut handles = Vec::with_capacity(REMOTE_NODE_LENGTH);
let node_vec = arc_mut!(Vec::with_capacity(REMOTE_NODE_LENGTH));
for (_, ip, _, rpc, zmq) in REMOTE_NODES {
let client = client.clone();
let ping = Arc::clone(&ping);
let node_vec = Arc::clone(&node_vec);
2022-11-21 01:21:47 +00:00
let request = Request::builder()
.method("POST")
.uri("http://".to_string() + ip + ":" + rpc + "/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::task::spawn(async move { Self::response(client, request, ip, ping, percent, node_vec).await; });
handles.push(handle);
}
for handle in handles {
handle.await?;
}
let node_vec = std::mem::take(&mut *lock!(node_vec));
let fastest_info = format!("Fastest node: {}ms ... {}", node_vec[0].ms, node_vec[0].ip);
2022-12-14 03:41:05 +00:00
let info = "Cleaning up connections".to_string();
info!("Ping | {}...", info);
let mut ping = lock!(ping);
ping.fastest = node_vec[0].ip;
ping.nodes = node_vec;
ping.msg = info;
drop(ping);
Ok(fastest_info)
2022-11-18 03:45:57 +00:00
}
async fn response(client: Client<HttpConnector>, request: Request<Body>, ip: &'static str, ping: Arc<Mutex<Self>>, percent: f32, node_vec: Arc<Mutex<Vec<NodeData>>>) {
let ms;
let info;
2022-12-14 03:41:05 +00:00
let now = Instant::now();
match tokio::time::timeout(Duration::from_secs(5), client.request(request)).await {
Ok(_) => {
ms = now.elapsed().as_millis();
info = format!("{}ms ... {}", ms, ip);
info!("Ping | {}", info)
},
Err(_) => {
ms = 5000;
info = format!("{}ms ... {}", ms, ip);
warn!("Ping | {}", info)
},
};
let color;
if ms < 300 {
color = GREEN;
} else if ms < 500 {
color = YELLOW;
} else if ms < 5000 {
color = RED;
} else {
color = BLACK;
}
let mut ping = lock!(ping);
ping.msg = info;
ping.prog += percent;
drop(ping);
lock!(node_vec).push(NodeData { ip, ms, color, });
}
}
//---------------------------------------------------------------------------------------------------- TESTS
#[cfg(test)]
mod test {
#[test]
fn validate_node_ips() {
for (_, ip, _, _, _) in crate::REMOTE_NODES {
assert!(ip.len() < 255);
assert!(ip.is_ascii());
assert!(ip.ends_with(":18081") || ip.ends_with(":18089"));
}
}
#[test]
fn spacing() {
for (_, ip, _, _, _) in crate::REMOTE_NODES {
assert!(crate::format_ip(ip).len() <= crate::REMOTE_NODE_MAX_CHARS);
}
}
}