mirror of
https://github.com/Cyrix126/gupaxx.git
synced 2024-11-16 23:37:47 +00:00
479 lines
14 KiB
Rust
479 lines
14 KiB
Rust
// Gupax - GUI Uniting P2Pool And XMRig
|
|
//
|
|
// Copyright (c) 2022-2023 hinto-janai
|
|
//
|
|
// 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::*;
|
|
use hyper::{
|
|
client::HttpConnector,
|
|
Client,Body,Request,
|
|
};
|
|
|
|
//---------------------------------------------------------------------------------------------------- Node list
|
|
// Remote Monero Nodes with ZMQ enabled, sourced from: [https://github.com/hinto-janai/monero-nodes]
|
|
// The format is an array of tuples consisting of: (IP, LOCATION, RPC_PORT, ZMQ_PORT)
|
|
|
|
pub const REMOTE_NODES: [(&str, &str, &str, &str); 24] = [
|
|
("monero.10z.com.ar", "AR - Buenos Aires F.D.", "18089", "18084"),
|
|
("escom.sadovo.com", "BG - Plovdiv", "18089", "18084"),
|
|
("monero2.10z.com.ar", "BR - São Paulo", "18089", "18083"),
|
|
("monero1.heitechsoft.com", "CA - Ontario", "18081", "18084"),
|
|
("node.monerodevs.org", "CA - Quebec", "18089", "18084"),
|
|
("xmr.aa78i2efsewr0neeknk.xyz", "DE - Bavaria", "18089", "18083"),
|
|
("de.poiuty.com", "DE - Berlin", "18081", "18084"),
|
|
("m1.poiuty.com", "DE - Berlin", "18081", "18084"),
|
|
("p2pmd.xmrvsbeast.com", "DE - Hesse", "18081", "18083"),
|
|
("reynald.ro", "FR - Île-de-France", "18089", "18084"),
|
|
("node2.monerodevs.org", "FR - Occitanie", "18089", "18084"),
|
|
("p2pool.uk", "GB - England", "18089", "18084"),
|
|
("monero.homeqloud.com", "GR - East Macedonia and Thrace", "18089", "18083"),
|
|
("home.allantaylor.kiwi", "NZ - Canterbury", "18089", "18083"),
|
|
("ru.poiuty.com", "RU - Kuzbass", "18081", "18084"),
|
|
("node-01-xmr.godevs.cloud", "SG - Singapore", "18089", "18083"),
|
|
("node-02-xmr.godevs.cloud", "SG - Singapore", "18089", "18083"),
|
|
("radishfields.hopto.org", "US - Colorado", "18081", "18084"),
|
|
("xmrbandwagon.hopto.org", "US - Colorado", "18081", "18084"),
|
|
("xmr.spotlightsound.com", "US - Kansas", "18081", "18084"),
|
|
("xmrnode.facspro.net", "US - Nebraska", "18089", "18084"),
|
|
("moneronode.ddns.net", "US - Pennsylvania", "18089", "18084"),
|
|
("node.richfowler.net", "US - Pennsylvania", "18089", "18084"),
|
|
("monero.jameswillhoite.com", "US - Ohio", "18089", "18084"),
|
|
];
|
|
|
|
pub const REMOTE_NODE_LENGTH: usize = REMOTE_NODES.len();
|
|
pub const REMOTE_NODE_MAX_CHARS: usize = 28; // xmr.aa78i2efsewr0neeknk.xyz
|
|
|
|
pub struct RemoteNode {
|
|
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 (ip, location, rpc, zmq) = REMOTE_NODES[0];
|
|
Self {
|
|
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].0.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 (ip, location, rpc, zmq) in REMOTE_NODES {
|
|
if from_ip == ip {
|
|
return Self { 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 (ip, location, rpc, zmq) = REMOTE_NODES[index];
|
|
Self { ip, location, rpc, zmq }
|
|
}
|
|
}
|
|
|
|
pub fn from_tuple(t: (&'static str, &'static str, &'static str, &'static str)) -> Self {
|
|
let (ip, location, rpc, zmq) = (t.0, t.1, t.2, t.3);
|
|
Self { 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].0;
|
|
while current_ip == node {
|
|
rng = thread_rng().gen_range(0..REMOTE_NODE_LENGTH);
|
|
node = REMOTE_NODES[rng].0;
|
|
}
|
|
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()
|
|
}
|
|
|
|
pub fn format_ip(ip: &str) -> String {
|
|
format!("{: >28}", ip)
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------------------- Node data
|
|
#[derive(Debug, Clone)]
|
|
pub struct NodeData {
|
|
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,
|
|
}
|
|
|
|
impl Default for Ping {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl Ping {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
nodes: NodeData::new_vec(),
|
|
fastest: REMOTE_NODES[0].0,
|
|
pinging: false,
|
|
msg: "No ping in progress".to_string(),
|
|
prog: 0.0,
|
|
pinged: false,
|
|
auto_selected: true,
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------------------- Main Ping function
|
|
// Intermediate function for spawning thread
|
|
pub fn spawn_thread(ping: &Arc<Mutex<Self>>) {
|
|
info!("Spawning ping thread...");
|
|
let ping = Arc::clone(ping);
|
|
std::thread::spawn(move|| {
|
|
let now = Instant::now();
|
|
match Self::ping(&ping) {
|
|
Ok(msg) => {
|
|
info!("Ping ... OK");
|
|
lock!(ping).msg = msg;
|
|
lock!(ping).pinged = true;
|
|
lock!(ping).auto_selected = false;
|
|
lock!(ping).prog = 100.0;
|
|
},
|
|
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;
|
|
});
|
|
}
|
|
|
|
// 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
|
|
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
|
|
let info = "Creating HTTP Client".to_string();
|
|
lock!(ping).msg = info;
|
|
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);
|
|
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);
|
|
|
|
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)
|
|
}
|
|
|
|
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;
|
|
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, location, rpc, zmq) in crate::REMOTE_NODES {
|
|
assert!(ip.len() < 255);
|
|
assert!(ip.is_ascii());
|
|
assert!(!location.is_empty());
|
|
assert!(!ip.is_empty());
|
|
assert!(rpc == "18081" || rpc == "18089");
|
|
assert!(zmq == "18083" || zmq == "18084");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn spacing() {
|
|
for (ip, _, _, _) in crate::REMOTE_NODES {
|
|
assert!(crate::format_ip(ip).len() == crate::REMOTE_NODE_MAX_CHARS);
|
|
}
|
|
}
|
|
|
|
// This one pings the IPs defined in [REMOTE_NODES] and fully serializes the JSON data to make sure they work.
|
|
// This will only be ran with be ran with [cargo test -- --ignored].
|
|
#[tokio::test]
|
|
#[ignore]
|
|
async fn full_ping() {
|
|
use hyper::{
|
|
client::HttpConnector,
|
|
Client,Body,Request,
|
|
};
|
|
use crate::{REMOTE_NODES,REMOTE_NODE_LENGTH};
|
|
use serde::{Serialize,Deserialize};
|
|
|
|
#[derive(Deserialize,Serialize)]
|
|
struct GetInfo {
|
|
id: String,
|
|
jsonrpc: String,
|
|
}
|
|
|
|
// Create HTTP client
|
|
let client: Client<HttpConnector> = Client::builder().build(HttpConnector::new());
|
|
|
|
// Random User Agent
|
|
let rand_user_agent = crate::Pkg::get_user_agent();
|
|
|
|
let mut n = 1;
|
|
for (ip, _, rpc, zmq) in REMOTE_NODES {
|
|
println!("[{}/{}] {} | {} | {}", n, REMOTE_NODE_LENGTH, ip, rpc, zmq);
|
|
let client = client.clone();
|
|
// Try 5 times before failure
|
|
let mut i = 1;
|
|
let mut response = loop {
|
|
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();
|
|
match client.request(request).await {
|
|
Ok(response) => break response,
|
|
Err(e) => {
|
|
println!("{:#?}", e);
|
|
if i > 5 { panic!("Node failure: {}:{}:{}", ip, rpc, zmq); }
|
|
std::thread::sleep(std::time::Duration::from_secs(3));
|
|
i += 1;
|
|
}
|
|
}
|
|
};
|
|
let body = hyper::body::to_bytes(response.body_mut()).await.unwrap();
|
|
let getinfo: GetInfo = serde_json::from_slice(&body).unwrap();
|
|
assert!(getinfo.id == "0");
|
|
assert!(getinfo.jsonrpc == "2.0");
|
|
n += 1;
|
|
}
|
|
}
|
|
}
|