From 9638f9dc5bcc0c0caa10905ba2b589c3097e33c0 Mon Sep 17 00:00:00 2001 From: hinto-janaiyo Date: Thu, 10 Nov 2022 21:20:31 -0500 Subject: [PATCH] p2pool: add [Ping] into GUI, add [simple], add address regex check --- Cargo.lock | 147 ++++++++-------- Cargo.toml | 4 +- README.md | 3 +- src/README.md | 16 +- src/constants.rs | 10 +- src/gupax.rs | 5 +- src/main.rs | 211 ++++++++++++----------- src/node.rs | 430 +++++++++++++++++++++++++++++------------------ src/p2pool.rs | 181 +++++++++++++++++--- src/state.rs | 4 + 10 files changed, 633 insertions(+), 378 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b66d5cc..63b854c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "Gupax" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", "arti-client", @@ -15,7 +15,7 @@ dependencies = [ "eframe", "egui", "egui_extras", - "env_logger 0.9.1", + "env_logger 0.9.3", "figment", "flate2", "hex-literal", @@ -112,9 +112,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e6e951cfbb2db8de1828d49073a113a29fd7117b1596caa781a258c7e38d72" +checksum = "bf6ccdb167abbf410dcb915cabd428929d7f6a04980b54a11f26a39f1c7f7107" dependencies = [ "cfg-if", "getrandom 0.2.8", @@ -294,9 +294,9 @@ dependencies = [ [[package]] name = "asynchronous-codec" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0de5164e5edbf51c45fb8c2d9664ae1c095cce1b265ecf7569093c0d66ef690" +checksum = "06a0daa378f5fd10634e44b0a29b2a87b890657658e072a30d6f26e57ddee182" dependencies = [ "bytes", "futures-sink", @@ -434,18 +434,18 @@ checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "bytemuck" -version = "1.12.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da" +checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9e1f5fa78f69496407a27ae9ed989e3c3b072310286f5ef385525e4cbc24a9" +checksum = "5fe233b960f12f8007e3db2d136e3cb1c291bfd7396e384ee76025fc1a3932b4" dependencies = [ "proc-macro2", "quote", @@ -487,9 +487,9 @@ dependencies = [ [[package]] name = "calloop" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a22a6a8f622f797120d452c630b0ab12e1331a1a753e2039ce7868d4ac77b4ee" +checksum = "595eb0438b3c6d262395fe30e6de9a61beb57ea56290b00a07f227fe6e20cbf2" dependencies = [ "log", "nix 0.24.2", @@ -506,9 +506,9 @@ checksum = "ceea694ffdf0118d2df95ace6fd9edfc6d27f88408d0d73b390f2d9e5699b3f2" [[package]] name = "cc" -version = "1.0.74" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" +checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" dependencies = [ "jobserver", ] @@ -603,9 +603,9 @@ dependencies = [ [[package]] name = "cocoa" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" +checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" dependencies = [ "bitflags", "block", @@ -893,9 +893,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a" +checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888" dependencies = [ "cc", "cxxbridge-flags", @@ -905,9 +905,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827" +checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3" dependencies = [ "cc", "codespan-reporting", @@ -920,15 +920,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a" +checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f" [[package]] name = "cxxbridge-macro" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7" +checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" dependencies = [ "proc-macro2", "quote", @@ -1187,9 +1187,9 @@ dependencies = [ [[package]] name = "educe" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c07b7cc9cd8c08d10db74fca3b20949b9b6199725c04a0cce6d543496098fcac" +checksum = "cb0188e3c3ba8df5753894d54461f0e39bc91741dc5b22e1c46999ec2c71f4e4" dependencies = [ "enum-ordinalize", "proc-macro2", @@ -1225,7 +1225,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc9fcd393c3daaaf5909008a1d948319d538b79c51871e4df0993260260a94e4" dependencies = [ - "ahash 0.8.0", + "ahash 0.8.2", "epaint", "nohash-hasher", "tracing", @@ -1297,9 +1297,9 @@ dependencies = [ [[package]] name = "enum-ordinalize" -version = "3.1.11" +version = "3.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2170fc0efee383079a8bdd05d6ea2a184d2a0f07a1c1dcabdb2fd5e9f24bc36c" +checksum = "a62bb1df8b45ecb7ffa78dca1c17a438fb193eb083db0b1b494d2a61bcb5096a" dependencies = [ "num-bigint", "num-traits", @@ -1324,9 +1324,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c90bf5f19754d10198ccb95b70664fc925bd1fc090a0fd9a6ebc54acc8cd6272" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" dependencies = [ "atty", "humantime 2.1.0", @@ -1342,7 +1342,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ba04741be7f6602b1a1b28f1082cce45948a7032961c52814f8946b28493300" dependencies = [ "ab_glyph", - "ahash 0.8.0", + "ahash 0.8.2", "atomic_refcell", "bytemuck", "emath", @@ -1983,9 +1983,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.20" +version = "0.14.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" dependencies = [ "bytes", "futures-channel", @@ -2110,9 +2110,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" +checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" [[package]] name = "itertools" @@ -2211,9 +2211,9 @@ checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" [[package]] name = "libloading" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ "cfg-if", "winapi", @@ -2221,9 +2221,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "292a948cd991e376cf75541fe5b97a1081d713c618b4f1b9500f8844e49eb565" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" [[package]] name = "libsqlite3-sys" @@ -2266,9 +2266,9 @@ dependencies = [ [[package]] name = "lzma-sys" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e06754c4acf47d49c727d5665ca9fb828851cda315ed3bd51edd148ef78a8772" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" dependencies = [ "cc", "libc", @@ -2292,9 +2292,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" +checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc" dependencies = [ "libc", ] @@ -2389,9 +2389,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ "lazy_static", "libc", @@ -2623,9 +2623,9 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ "hermit-abi", "libc", @@ -2652,15 +2652,6 @@ dependencies = [ "syn", ] -[[package]] -name = "num_threads" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" -dependencies = [ - "libc", -] - [[package]] name = "objc" version = "0.2.7" @@ -2964,14 +2955,14 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "png" -version = "0.17.6" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f0e7f4c94ec26ff209cee506314212639d6c91b80afb82984819fafce9df01c" +checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" dependencies = [ "bitflags", "crc32fast", "flate2", - "miniz_oxide 0.5.4", + "miniz_oxide 0.6.2", ] [[package]] @@ -2991,9 +2982,9 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro-crate" @@ -3171,9 +3162,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" dependencies = [ "aho-corasick", "memchr", @@ -3182,9 +3173,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "remove_dir_all" @@ -3291,7 +3282,7 @@ dependencies = [ "hashlink", "libsqlite3-sys", "smallvec", - "time 0.3.16", + "time 0.3.17", ] [[package]] @@ -3378,9 +3369,9 @@ dependencies = [ [[package]] name = "scoped-tls" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scoped_threadpool" @@ -3647,7 +3638,7 @@ dependencies = [ "num-bigint", "num-traits", "thiserror", - "time 0.3.16", + "time 0.3.17", ] [[package]] @@ -3911,13 +3902,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fab5c8b9980850e06d92ddbe3ab839c062c801f3927c0fb8abd6fc8e918fbca" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ "itoa", - "libc", - "num_threads", "serde", "time-core", "time-macros", @@ -3931,9 +3920,9 @@ checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] name = "time-macros" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bb801831d812c562ae7d2bfb531f26e66e4e1f6b17307ba4149c5064710e5b" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" dependencies = [ "time-core", ] @@ -4356,7 +4345,7 @@ dependencies = [ "serde", "signature", "thiserror", - "time 0.3.16", + "time 0.3.17", "tor-basic-utils", "tor-checkable", "tor-circmgr", @@ -4513,7 +4502,7 @@ dependencies = [ "serde_with", "signature", "thiserror", - "time 0.3.16", + "time 0.3.17", "tinystr", "tor-bytes", "tor-cert", @@ -5341,7 +5330,7 @@ dependencies = [ "hmac", "pbkdf2", "sha1", - "time 0.3.16", + "time 0.3.17", "zstd", ] diff --git a/Cargo.toml b/Cargo.toml index 088c556..c1d11aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "Gupax" -version = "0.1.0" -authors = ["hinto-janaiyo "] +version = "0.2.0" +authors = ["hinto-janaiyo "] description = "GUI for P2Pool+XMRig" documentation = "https://github.com/hinto-janaiyo/gupax" edition = "2021" diff --git a/README.md b/README.md index f3eec8d..7d96f05 100644 --- a/README.md +++ b/README.md @@ -88,11 +88,10 @@ https://user-images.githubusercontent.com/101352116/194763334-d8e936c9-a71e-474e * A Monero node/wallet ## Build -Windows/macOS/Linux: ``` cargo build --release ``` -On macOS, if you want the binary to have an icon in `Finder`, you must install [`cargo-bundle`](https://github.com/burtonageo/cargo-bundle) and compile uwith: +On macOS, if you want the binary to have an icon in `Finder`, you must install [`cargo-bundle`](https://github.com/burtonageo/cargo-bundle) and compile with: ``` cargo bundle --release ``` diff --git a/src/README.md b/src/README.md index 67fcfee..b9bf19d 100644 --- a/src/README.md +++ b/src/README.md @@ -8,13 +8,15 @@ | File/Folder | Purpose | |----------------|---------| | `constants.rs` | General constants needed in Gupax -| `gupax.rs` | Impl for `Gupax` tab -| `main.rs` | Struct/enum/impl for `App/Tab/State`, init functions, main function -| `node.rs` | Struct/impl for Community Nodes -| `p2pool.rs` | Impl for `P2Pool` tab -| `state.rs` | Struct/impl for `gupax.toml`, the disk state. This holds the structs representing tabs with mutable state (Gupax/P2Pool/XMRig) -| `status.rs` | Struct/impl for `Status` tab -| `xmrig.rs` | Impl for `XMRig` tab +| `ferris.rs` | Cute crab `--ferris` +| `gupax.rs` | `Gupax` tab +| `main.rs` | `App/Tab/State` + misc functions +| `node.rs` | Community node feature +| `p2pool.rs` | `P2Pool` tab +| `state.rs` | `gupax.toml` config code. This holds the structs representing tabs with mutable state (Gupax/P2Pool/XMRig) +| `status.rs` | `Status` tab +| `update.rs` | Update code for the `Gupax` tab +| `xmrig.rs` | `XMRig` tab ## Bootstrap This is how Gupax works internally when starting up, divided into 3 sections. diff --git a/src/constants.rs b/src/constants.rs index bedc9ae..6be837d 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -69,13 +69,19 @@ pub const GUPAX_SAVE_BEFORE_QUIT: &'static str = "Automatically save any changed pub const GUPAX_PATH_P2POOL: &'static str = "The location of the P2Pool binary, both absolute and relative paths are accepted"; pub const GUPAX_PATH_XMRIG: &'static str = "The location of the XMRig binary, both absolute and relative paths are accepted"; // P2Pool -pub const P2POOL_MAIN: &'static str = "The P2Pool main-chain. This P2Pool finds shares faster, but has a higher difficulty. Suitable for miners with more than 50kH/s"; -pub const P2POOL_MINI: &'static str = "The P2Pool mini-chain. This P2Pool finds shares slower, but has a lower difficulty. Suitable for miners with less than 50kH/s"; +pub const P2POOL_MAIN: &'static str = "Use the P2Pool main-chain. This P2Pool finds shares faster, but has a higher difficulty. Suitable for miners with more than 50kH/s"; +pub const P2POOL_MINI: &'static str = "Use the P2Pool mini-chain. This P2Pool finds shares slower, but has a lower difficulty. Suitable for miners with less than 50kH/s"; pub const P2POOL_OUT: &'static str = "How many out-bound peers (you connecting to others) to connect to?"; pub const P2POOL_IN: &'static str = "How many in-bound peers (others connecting to you) to connect to?"; pub const P2POOL_LOG: &'static str = "Verbosity of the console log"; pub const P2POOL_COMMUNITY: &'static str = "Connect to a community trusted Monero node: This is convenient because you don't have to download the Monero blockchain but it comes at the cost of privacy"; pub const P2POOL_MANUAL: &'static str = "Manually specify your own Monero node settings"; +pub const P2POOL_AUTO_NODE: &'static str = "Automatically ping the community Monero nodes at Gupax startup"; +pub const P2POOL_AUTO_SELECT: &'static str = "Automatically select the fastest community Monero node after pinging"; +pub const P2POOL_SELECT_FASTEST: &'static str = "Select the fastest community Monero node"; +pub const P2POOL_PING: &'static str = "Ping the built-in community Monero nodes"; +pub const P2POOL_ADDRESS: &'static str = "You must use a primary Monero address to mine on P2Pool (starts with a 4). It is highly recommended to create a new wallet for P2Pool mining; wallet addresses are public on P2Pool!"; + // XMRig pub const XMRIG_P2POOL: &'static str = "Mine to your own P2Pool instance (localhost:3333)"; pub const XMRIG_MANUAL: &'static str = "Manually specify where to mine to"; diff --git a/src/gupax.rs b/src/gupax.rs index 04efe93..35043de 100644 --- a/src/gupax.rs +++ b/src/gupax.rs @@ -17,7 +17,8 @@ use std::path::Path; use crate::{App,State}; -use egui::WidgetType::Button; +use egui::TextStyle::Monospace; +use egui::RichText; use crate::constants::*; use crate::state::{Gupax,Version}; use crate::update::*; @@ -73,7 +74,7 @@ impl Gupax { ui.set_enabled(updating); let prog = *update.lock().unwrap().prog.lock().unwrap(); let msg = format!("{}\n{}{}", *update.lock().unwrap().msg.lock().unwrap(), prog, "%"); - ui.add_sized([width, height*1.4], egui::Label::new(msg)); + ui.add_sized([width, height*1.4], egui::Label::new(RichText::text_style(RichText::new(msg), Monospace))); let height = height/2.0; if updating { ui.add_sized([width, height], egui::Spinner::new().size(height)); diff --git a/src/main.rs b/src/main.rs index 8a4d7ed..34de8cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,6 +34,9 @@ use eframe::{egui,NativeOptions}; use log::*; use env_logger::{Builder,WriteStyle}; +// Regex +use regex::Regex; + // std use std::io::Write; use std::process::exit; @@ -64,9 +67,7 @@ pub struct App { tab: Tab, // What tab are we on? quit: bool, // Was the quit button clicked? quit_confirm: bool, // Was the quit confirmed? - ping: bool, // Was the ping button clicked? - pinging: Arc>, // Is a ping in progress? - node: Arc>, // Data on community nodes + ping: Arc>, // Ping data found in [node.rs] width: f32, // Top-level width height: f32, // Top-level height // State @@ -88,9 +89,10 @@ pub struct App { dir: String, // Directory [Gupax] binary is in resolution: Vec2, // Frame resolution os: &'static str, // OS - version: String, // Gupax version + version: &'static str, // Gupax version name_version: String, // [Gupax vX.X.X] banner: RetainedImage, // Gupax banner image + addr_regex: Regex, // [4.*] Monero Address Regex } impl App { @@ -108,11 +110,9 @@ impl App { tab: Tab::default(), quit: false, quit_confirm: false, - ping: false, - pinging: Arc::new(Mutex::new(false)), + ping: Arc::new(Mutex::new(Ping::new())), width: 1280.0, height: 720.0, - node: Arc::new(Mutex::new(NodeStruct::default())), og: Arc::new(Mutex::new(State::default())), state: State::default(), update: Arc::new(Mutex::new(Update::new(String::new(), PathBuf::new(), PathBuf::new(), true))), @@ -126,9 +126,10 @@ impl App { dir: "".to_string(), resolution: Vec2::new(1280.0, 720.0), os: OS, - version: format!("{}", GUPAX_VERSION), + version: GUPAX_VERSION, name_version: format!("Gupax {}", GUPAX_VERSION), - banner: RetainedImage::from_image_bytes("banner.png", BYTES_BANNER).expect("oops"), + banner: RetainedImage::from_image_bytes("banner.png", BYTES_BANNER).unwrap(), + addr_regex: Regex::new("^4[A-Za-z1-9]+$").unwrap(), }; // Apply arg state let mut app = parse_args(app); @@ -188,21 +189,15 @@ fn init_text_styles(ctx: &egui::Context, width: f32) { let scale = width / 26.666; let mut style = (*ctx.style()).clone(); style.text_styles = [ -// (Small, FontId::new(10.0, Proportional)), -// (Body, FontId::new(25.0, Proportional)), -// (Button, FontId::new(25.0, Proportional)), -// (Monospace, FontId::new(25.0, Proportional)), -// (Heading, FontId::new(30.0, Proportional)), -// (Name("Tab".into()), FontId::new(50.0, Proportional)), -// (Name("Bottom".into()), FontId::new(25.0, Proportional)), (Small, FontId::new(scale/3.0, Proportional)), (Body, FontId::new(scale/2.0, Proportional)), (Button, FontId::new(scale/2.0, Proportional)), - (Monospace, FontId::new(scale/2.0, Proportional)), + (Monospace, FontId::new(scale/2.0, egui::FontFamily::Monospace)), (Heading, FontId::new(scale/1.5, Proportional)), (Name("Tab".into()), FontId::new(scale*1.2, Proportional)), (Name("Bottom".into()), FontId::new(scale/2.0, Proportional)), ].into(); +// style.visuals.selection.stroke = Stroke { width: 5.0, color: Color32::from_rgb(255, 255, 255) }; // style.spacing.slider_width = scale; // style.spacing.text_edit_width = scale; // style.spacing.button_padding = Vec2::new(scale/2.0, scale/2.0); @@ -211,6 +206,14 @@ fn init_text_styles(ctx: &egui::Context, width: f32) { ctx.request_repaint(); } +//fn init_color(ctx: &egui::Context) { +// let mut style = (*ctx.style()).clone(); +// style.visuals.widgets.inactive.fg_stroke.color = Color32::from_rgb(100, 100, 100); +// style.visuals.selection.bg_fill = Color32::from_rgb(255, 125, 50); +// style.visuals.selection.stroke = Stroke { width: 5.0, color: Color32::from_rgb(255, 255, 255) }; +// ctx.set_style(style); +//} + fn init_logger() { // #[cfg(debug_assertions)] let filter = LevelFilter::Info; @@ -395,6 +398,9 @@ impl eframe::App for App { // *-------* // | DEBUG | // *-------* +// crate::node::ping(); +// std::process::exit(0); +// init_color(ctx); // This sets the top level Ui dimensions. // Used as a reference for other uis. @@ -413,6 +419,11 @@ impl eframe::App for App { // contains Arc's that cannot be compared easily. // They don't need to be compared anyway. let og = self.og.lock().unwrap().clone(); + // The [P2Pool Node] selection needs to be the same + // for both [State] and [Og] because of [Auto-select] + // Wrapping [node] within an [Arc] is a lot more work + // so sending it into the [Ping] thread is not viable. + self.state.p2pool.node = self.og.lock().unwrap().p2pool.node; if og.gupax != self.state.gupax || og.p2pool != self.state.p2pool || og.xmrig != self.state.xmrig { self.diff = true; } else { @@ -503,10 +514,7 @@ impl eframe::App for App { ui.style_mut().override_text_style = Some(Name("Tab".into())); ui.style_mut().visuals.widgets.inactive.fg_stroke.color = Color32::from_rgb(100, 100, 100); ui.style_mut().visuals.selection.bg_fill = Color32::from_rgb(255, 120, 120); - ui.style_mut().visuals.selection.stroke = Stroke { - width: 5.0, - color: Color32::from_rgb(255, 255, 255), - }; + ui.style_mut().visuals.selection.stroke = Stroke { width: 5.0, color: Color32::from_rgb(255, 255, 255) }; if ui.add_sized([width, height], egui::SelectableLabel::new(self.tab == Tab::About, "About")).clicked() { self.tab = Tab::About; } ui.separator(); if ui.add_sized([width, height], egui::SelectableLabel::new(self.tab == Tab::Status, "Status")).clicked() { self.tab = Tab::Status; } @@ -523,104 +531,113 @@ impl eframe::App for App { // Bottom: app info + state/process buttons egui::TopBottomPanel::bottom("bottom").show(ctx, |ui| { - let width = self.width/8.0; let height = self.height/18.0; ui.style_mut().override_text_style = Some(Name("Bottom".into())); ui.horizontal(|ui| { ui.group(|ui| { + // [Gupax Version] + [OS] + [P2Pool on/off] + [XMRig on/off] + let width = ((self.width/2.0)/4.0)-(SPACE*2.0); ui.add_sized([width, height], Label::new(&*self.name_version)); ui.separator(); ui.add_sized([width, height], Label::new(self.os)); ui.separator(); - ui.add_sized([width/1.5, height], Label::new("P2Pool")); if self.p2pool { - ui.add_sized([width/4.0, height], Label::new(RichText::new("⏺").color(Color32::from_rgb(100, 230, 100)))); + ui.add_sized([width, height], Label::new(RichText::new("P2Pool ⏺").color(Color32::from_rgb(100, 230, 100)))); } else { - ui.add_sized([width/4.0, height], Label::new(RichText::new("⏺").color(Color32::from_rgb(230, 50, 50)))); + ui.add_sized([width, height], Label::new(RichText::new("P2Pool ⏺").color(Color32::from_rgb(230, 50, 50)))); } ui.separator(); - ui.add_sized([width/1.5, height], Label::new("XMRig")); if self.xmrig { - ui.add_sized([width/4.0, height], Label::new(RichText::new("⏺").color(Color32::from_rgb(100, 230, 100)))); + ui.add_sized([width, height], Label::new(RichText::new("XMRig ⏺").color(Color32::from_rgb(100, 230, 100)))); } else { - ui.add_sized([width/4.0, height], Label::new(RichText::new("⏺").color(Color32::from_rgb(230, 50, 50)))); + ui.add_sized([width, height], Label::new(RichText::new("XMRig ⏺").color(Color32::from_rgb(230, 50, 50)))); } }); ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| { - ui.group(|ui| { - if self.diff == false { - ui.set_enabled(false) - } - let width = width / 2.0; - if ui.add_sized([width, height], egui::Button::new("Save")).on_hover_text("Save changes").clicked() { - self.og.lock().unwrap().gupax = self.state.gupax.clone(); - self.og.lock().unwrap().p2pool = self.state.p2pool.clone(); - self.og.lock().unwrap().xmrig = self.state.xmrig.clone(); - self.og.lock().unwrap().save(); - } - if ui.add_sized([width, height], egui::Button::new("Reset")).on_hover_text("Reset changes").clicked() { - self.state.gupax = self.og.lock().unwrap().gupax.clone(); - self.state.p2pool = self.og.lock().unwrap().p2pool.clone(); - self.state.xmrig = self.og.lock().unwrap().xmrig.clone(); - } - }); - - let width = (ui.available_width() / 3.0) - 6.2; - match self.tab { - Tab::P2pool => { - ui.group(|ui| { - if self.p2pool { - if ui.add_sized([width, height], egui::Button::new("⟲")).on_hover_text("Restart P2Pool").clicked() { self.p2pool = false; } - if ui.add_sized([width, height], egui::Button::new("⏹")).on_hover_text("Stop P2Pool").clicked() { self.p2pool = false; } - ui.add_enabled_ui(false, |ui| { - ui.add_sized([width, height], egui::Button::new("⏺")).on_hover_text("Start P2Pool"); - }); - } else { - ui.add_enabled_ui(false, |ui| { - ui.add_sized([width, height], egui::Button::new("⟲")).on_hover_text("Restart P2Pool"); - ui.add_sized([width, height], egui::Button::new("⏹")).on_hover_text("Stop P2Pool"); - }); - if ui.add_sized([width, height], egui::Button::new("⏺")).on_hover_text("Start P2Pool").clicked() { self.p2pool = true; } - } - }); - } - Tab::Xmrig => { - ui.group(|ui| { - if self.xmrig { - if ui.add_sized([width, height], egui::Button::new("⟲")).on_hover_text("Restart XMRig").clicked() { self.xmrig = false; } - if ui.add_sized([width, height], egui::Button::new("⏹")).on_hover_text("Stop XMRig").clicked() { self.xmrig = false; } - ui.add_enabled_ui(false, |ui| { - ui.add_sized([width, height], egui::Button::new("⏺")).on_hover_text("Start XMRig"); - }); - } else { - ui.add_enabled_ui(false, |ui| { - ui.add_sized([width, height], egui::Button::new("⟲")).on_hover_text("Restart XMRig"); - ui.add_sized([width, height], egui::Button::new("⏹")).on_hover_text("Stop XMRig"); - }); - if ui.add_sized([width, height], egui::Button::new("⏺")).on_hover_text("Start XMRig").clicked() { self.xmrig = true; } - } - }); - } - _ => (), + // [Start/Stop/Restart] + [Simple/Advanced] + [Save/Reset] + let width = (ui.available_width()/3.0)-(SPACE*3.0); + ui.group(|ui| { + if self.diff == false { + ui.set_enabled(false) + } + let width = width / 2.0; + if ui.add_sized([width, height], egui::Button::new("Reset")).on_hover_text("Reset changes").clicked() { + self.state.gupax = self.og.lock().unwrap().gupax.clone(); + self.state.p2pool = self.og.lock().unwrap().p2pool.clone(); + self.state.xmrig = self.og.lock().unwrap().xmrig.clone(); + } + if ui.add_sized([width, height], egui::Button::new("Save")).on_hover_text("Save changes").clicked() { + self.og.lock().unwrap().gupax = self.state.gupax.clone(); + self.og.lock().unwrap().p2pool = self.state.p2pool.clone(); + self.og.lock().unwrap().xmrig = self.state.xmrig.clone(); + self.og.lock().unwrap().save(); } }); + + match self.tab { + Tab::P2pool => { + ui.group(|ui| { + let width = width / 1.5; + if ui.add_sized([width, height], egui::SelectableLabel::new(!self.state.p2pool.simple, "Advanced")).clicked() { + self.state.p2pool.simple = false; + } + ui.separator(); + if ui.add_sized([width, height], egui::SelectableLabel::new(self.state.p2pool.simple, "Simple")).clicked() { + self.state.p2pool.simple = true; + } + }); + ui.group(|ui| { + let width = (ui.available_width()/3.0)-5.0; + if self.p2pool { + if ui.add_sized([width, height], egui::Button::new("⟲")).on_hover_text("Restart P2Pool").clicked() { self.p2pool = false; } + if ui.add_sized([width, height], egui::Button::new("⏹")).on_hover_text("Stop P2Pool").clicked() { self.p2pool = false; } + ui.add_enabled_ui(false, |ui| { + ui.add_sized([width, height], egui::Button::new("⏺")).on_hover_text("Start P2Pool"); + }); + } else { + ui.add_enabled_ui(false, |ui| { + ui.add_sized([width, height], egui::Button::new("⟲")).on_hover_text("Restart P2Pool"); + ui.add_sized([width, height], egui::Button::new("⏹")).on_hover_text("Stop P2Pool"); + }); + if ui.add_sized([width, height], egui::Button::new("⏺")).on_hover_text("Start P2Pool").clicked() { self.p2pool = true; } + } + }); + } + Tab::Xmrig => { + ui.group(|ui| { + let width = width / 1.5; + if ui.add_sized([width, height], egui::SelectableLabel::new(!self.state.xmrig.simple, "Advanced")).clicked() { + self.state.xmrig.simple = false; + } + ui.separator(); + if ui.add_sized([width, height], egui::SelectableLabel::new(self.state.xmrig.simple, "Simple")).clicked() { + self.state.xmrig.simple = true; + } + }); + ui.group(|ui| { + let width = (ui.available_width()/3.0)-5.0; + if self.xmrig { + if ui.add_sized([width, height], egui::Button::new("⟲")).on_hover_text("Restart XMRig").clicked() { self.xmrig = false; } + if ui.add_sized([width, height], egui::Button::new("⏹")).on_hover_text("Stop XMRig").clicked() { self.xmrig = false; } + ui.add_enabled_ui(false, |ui| { + ui.add_sized([width, height], egui::Button::new("⏺")).on_hover_text("Start XMRig"); + }); + } else { + ui.add_enabled_ui(false, |ui| { + ui.add_sized([width, height], egui::Button::new("⟲")).on_hover_text("Restart XMRig"); + ui.add_sized([width, height], egui::Button::new("⏹")).on_hover_text("Stop XMRig"); + }); + if ui.add_sized([width, height], egui::Button::new("⏺")).on_hover_text("Start XMRig").clicked() { self.xmrig = true; } + } + }); + } + _ => (), + } + }); }); }); - // If ping was pressed, start thread - if self.ping { - self.ping = false; - self.pinging = Arc::new(Mutex::new(true)); - let node_clone = Arc::clone(&self.node); - let pinging_clone = Arc::clone(&self.pinging); - thread::spawn(move|| { - let result = NodeStruct::ping(); - *node_clone.lock().unwrap() = result.nodes; - *pinging_clone.lock().unwrap() = false; - }); - } - // Middle panel, contents of the [Tab] egui::CentralPanel::default().show(ctx, |ui| { // This sets the Ui dimensions after Top/Bottom are filled @@ -655,7 +672,7 @@ impl eframe::App for App { Gupax::show(&mut self.state.gupax, &self.og, &self.state.version, &self.update, self.width, self.height, ctx, ui); } Tab::P2pool => { - P2pool::show(&mut self.state.p2pool, self.width, self.height, ctx, ui); + P2pool::show(&mut self.state.p2pool, &self.og, self.p2pool, &self.ping, &self.addr_regex, self.width, self.height, ctx, ui); } Tab::Xmrig => { Xmrig::show(&mut self.state.xmrig, self.width, self.height, ctx, ui); diff --git a/src/node.rs b/src/node.rs index bbfdbd5..c2d5b06 100644 --- a/src/node.rs +++ b/src/node.rs @@ -20,10 +20,13 @@ use std::time::{Instant,Duration}; use std::collections::HashMap; use std::error::Error; use std::thread; +use std::sync::{Arc,Mutex}; use egui::Color32; use rand::Rng; use log::*; +use reqwest::blocking::ClientBuilder; +//---------------------------------------------------------------------------------------------------- Node list // Community Monerod nodes. All of these have ZMQ on 18083. // Adding/removing nodes will need changes to pretty // much all the code in this file, and the code that @@ -33,37 +36,27 @@ pub const CAKE: &'static str = "xmr-node.cakewallet.com:18081"; pub const CAKE_EU: &'static str = "xmr-node-eu.cakewallet.com:18081"; pub const CAKE_UK: &'static str = "xmr-node-uk.cakewallet.com:18081"; pub const CAKE_US: &'static str = "xmr-node-usa-east.cakewallet.com:18081"; +pub const MAJESTICBANK_IS: &'static str = "node.majesticbank.is:18089"; +pub const MAJESTICBANK_SU: &'static str = "node.majesticbank.su:18089"; pub const MONERUJO: &'static str = "nodex.monerujo.io:18081"; pub const RINO: &'static str = "node.community.rino.io:18081"; -pub const SELSTA: &'static str = "selsta1.featherwallet.net:18081"; +pub const SELSTA_1: &'static str = "selsta1.featherwallet.net:18081"; +pub const SELSTA_2: &'static str = "selsta2.featherwallet.net:18081"; pub const SETH: &'static str = "node.sethforprivacy.com:18089"; pub const SUPPORTXMR: &'static str = "node.supportxmr.com:18081"; pub const SUPPORTXMR_IR: &'static str = "node.supportxmr.ir:18081"; +pub const SINGAPORE: &'static str = "singapore.node.xmr.pm:18089"; pub const XMRVSBEAST: &'static str = "p2pmd.xmrvsbeast.com:18081"; -pub const NODE_IPS: [&'static str; 12] = [ - C3POOL,CAKE,CAKE_EU,CAKE_UK,CAKE_US,MONERUJO,RINO, - SELSTA,SETH,SUPPORTXMR,SUPPORTXMR_IR,XMRVSBEAST, +pub const NODE_IPS: [&'static str; 16] = [ + C3POOL,CAKE,CAKE_EU,CAKE_UK,CAKE_US,MAJESTICBANK_IS,MAJESTICBANK_SU,MONERUJO, + RINO,SELSTA_1,SELSTA_2,SETH,SUPPORTXMR,SUPPORTXMR_IR,SINGAPORE,XMRVSBEAST, ]; -#[derive(Debug)] -pub struct NodeStruct { - c3pool: Data, cake: Data, cake_eu: Data, cake_uk: Data, cake_us: Data, monerujo: Data, - rino: Data, selsta: Data, seth: Data, supportxmr: Data, supportxmr_ir: Data, xmrvsbeast: Data, -} - -#[derive(Debug)] -pub struct Data { - pub ms: u128, - pub color: Color32, - pub id: NodeEnum, - pub ip: &'static str, -} - #[derive(Copy,Clone,Eq,PartialEq,Debug,Deserialize,Serialize)] pub enum NodeEnum { - C3pool, Cake, CakeEu, CakeUk, CakeUs, Monerujo, Rino, - Selsta, Seth, SupportXmr, SupportXmrIr, XmrVsBeast, + C3pool,Cake,CakeEu,CakeUk,CakeUs,MajesticBankIs,MajesticBankSu,Monerujo, + Rino,Selsta1,Selsta2,Seth,SupportXmr,SupportXmrIr,Singapore,XmrVsBeast, } impl std::fmt::Display for NodeEnum { @@ -72,152 +65,265 @@ impl std::fmt::Display for NodeEnum { } } +//---------------------------------------------------------------------------------------------------- Node data +#[derive(Debug)] +pub struct NodeData { + pub id: NodeEnum, + pub ip: &'static str, + pub ms: u128, + pub color: Color32, +} + +impl NodeData { + pub fn new_vec() -> Vec { + let mut vec = Vec::new(); + for ip in NODE_IPS.iter() { + vec.push(Self { + id: ip_to_enum(ip), + ip, + ms: 0, + color: Color32::LIGHT_GRAY, + }); + } + vec + } +} + +//---------------------------------------------------------------------------------------------------- Ping data +#[derive(Debug)] +pub struct Ping { + pub nodes: Vec, + pub fastest: NodeEnum, + pub pinging: bool, + pub msg: String, + pub prog: f32, + pub pinged: bool, +} + +impl Ping { + pub fn new() -> Self { + Self { + nodes: NodeData::new_vec(), + fastest: NodeEnum::C3pool, + pinging: false, + msg: "No ping in progress".to_string(), + prog: 0.0, + pinged: false, + } + } +} + #[derive(Debug)] pub struct PingResult { - pub nodes: NodeStruct, + pub nodes: Vec, pub fastest: NodeEnum, } -use crate::NodeEnum::*; -impl NodeStruct { - pub fn default() -> Self { - let ms = 0; - let color = Color32::GRAY; - Self { - c3pool: Data { ms, color, id: C3pool, ip: C3POOL, }, - cake: Data { ms, color, id: Cake, ip: CAKE, }, - cake_eu: Data { ms, color, id: CakeEu, ip: CAKE_EU, }, - cake_uk: Data { ms, color, id: CakeUk, ip: CAKE_UK, }, - cake_us: Data { ms, color, id: CakeUs, ip: CAKE_US, }, - monerujo: Data { ms, color, id: Monerujo, ip: MONERUJO, }, - rino: Data { ms, color, id: Rino, ip: RINO, }, - selsta: Data { ms, color, id: Selsta, ip: SELSTA, }, - seth: Data { ms, color, id: Seth, ip: SETH, }, - supportxmr: Data { ms, color, id: SupportXmr, ip: SUPPORTXMR, }, - supportxmr_ir: Data { ms, color, id: SupportXmrIr, ip: SUPPORTXMR_IR, }, - xmrvsbeast: Data { ms, color, id: XmrVsBeast, ip: XMRVSBEAST, }, - } - } - - // This is for pinging the community nodes to - // find the fastest/slowest one for the user. - // The process: - // - Send [get_info] JSON-RPC requests over HTTP - // - To prevent fingerprinting, randomly send [2-4] calls - // - 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(NodeStruct, NodeEnum)] (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 - pub fn ping() -> PingResult { - info!("Starting community node pings..."); - // Get node list - let mut nodes = NodeStruct::default(); - - // 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<(u128, NodeEnum)> = Vec::new(); - - // Create HTTP Client - let timeout_sec = Duration::from_millis(5000); - let client = reqwest::blocking::ClientBuilder::new(); - let client = reqwest::blocking::ClientBuilder::timeout(client, timeout_sec); - let client = reqwest::blocking::ClientBuilder::build(client).unwrap(); - - for ip in NODE_IPS.iter() { - // Match IP - let id = match *ip { - C3POOL => C3pool, - CAKE => Cake, - CAKE_EU => CakeEu, - CAKE_UK => CakeUk, - CAKE_US => CakeUs, - MONERUJO => Monerujo, - RINO => Rino, - SELSTA => Selsta, - SETH => Seth, - SUPPORTXMR => SupportXmr, - SUPPORTXMR_IR => SupportXmrIr, - _ => XmrVsBeast, - }; - // Misc - let mut timeout = false; - let mut mid = Duration::new(0, 0); - let max = rand::thread_rng().gen_range(2..4); - - // Start JSON-RPC request - for i in 1..=max { - let now = Instant::now(); - let http = "http://".to_owned() + &**ip + "/json_rpc"; - match client.post(http).json(&get_info).send() { - Ok(_) => mid += now.elapsed(), - Err(err) => { - error!("Timeout on [{:#?}: {}] (over 5 seconds) | {}", id, ip, err); - mid += timeout_sec; - timeout = true; - }, - }; - } - - // Calculate average - let ms = mid.as_millis() / 3; - vec.push((ms, id)); - info!("{}ms ... {} calls ... {}", ms, max, ip); - let color: Color32; - if timeout == true { - color = Color32::BLACK - } else if ms >= 1000 { - color = Color32::LIGHT_RED - } else if ms >= 300 { - color = Color32::LIGHT_YELLOW - } else { - color = Color32::LIGHT_GREEN - } - match id { - C3pool => { nodes.c3pool.ms = ms; nodes.c3pool.color = color; }, - Cake => { nodes.cake.ms = ms; nodes.cake.color = color; }, - CakeEu => { nodes.cake_eu.ms = ms; nodes.cake_eu.color = color; }, - CakeUk => { nodes.cake_uk.ms = ms; nodes.cake_uk.color = color; }, - CakeUs => { nodes.cake_us.ms = ms; nodes.cake_us.color = color; }, - Monerujo => { nodes.monerujo.ms = ms; nodes.monerujo.color = color; }, - Rino => { nodes.rino.ms = ms; nodes.rino.color = color; }, - Selsta => { nodes.selsta.ms = ms; nodes.selsta.color = color; }, - Seth => { nodes.seth.ms = ms; nodes.seth.color = color; }, - SupportXmr => { nodes.supportxmr.ms = ms; nodes.supportxmr.color = color; }, - SupportXmrIr => { nodes.supportxmr_ir.ms = ms; nodes.supportxmr_ir.color = color; }, - XmrVsBeast => { nodes.xmrvsbeast.ms = ms; nodes.xmrvsbeast.color = color; }, - } - } - - // Calculate fastest out of all nodes - let mut best_ms: u128 = vec[0].0; - let mut fastest: NodeEnum = vec[0].1; - for (ms, id) in vec.iter() { - if ms < &best_ms { - fastest = *id; - best_ms = *ms; - } - } - // These values have weird behavior. - // The values don't update if not printed beforehand, - // so the match below on [fastest] gets funky. - info!("Fastest node ... {:#?} @ {:#?}ms", fastest, best_ms); - info!("Community node ping ... OK"); - PingResult { nodes, fastest, } +//---------------------------------------------------------------------------------------------------- IP <-> Enum functions +// Function for returning IP/Enum +pub fn ip_to_enum(ip: &'static str) -> NodeEnum { + match ip { + C3POOL => C3pool, + CAKE => Cake, + CAKE_EU => CakeEu, + CAKE_UK => CakeUk, + CAKE_US => CakeUs, + MAJESTICBANK_IS => MajesticBankIs, + MAJESTICBANK_SU => MajesticBankSu, + MONERUJO => Monerujo, + RINO => Rino, + SELSTA_1 => Selsta1, + SELSTA_2 => Selsta2, + SETH => Seth, + SINGAPORE => Singapore, + SUPPORTXMR => SupportXmr, + SUPPORTXMR_IR => SupportXmrIr, + _ => XmrVsBeast, } } + +pub fn enum_to_ip(node: NodeEnum) -> &'static str { + match node { + C3pool => C3POOL, + Cake => CAKE, + CakeEu => CAKE_EU, + CakeUk => CAKE_UK, + CakeUs => CAKE_US, + MajesticBankIs => MAJESTICBANK_IS, + MajesticBankSu => MAJESTICBANK_SU, + Monerujo => MONERUJO, + Rino => RINO, + Selsta1 => SELSTA_1, + Selsta2 => SELSTA_2, + Seth => SETH, + Singapore => SINGAPORE, + SupportXmr => SUPPORTXMR, + SupportXmrIr => SUPPORTXMR_IR, + _ => XMRVSBEAST + } +} + +// 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), + } +} + +// MajesticBankIs = 14 max length +pub fn format_enum(id: NodeEnum) -> String { + match id.to_string().len() { + 1 => format!("{} ", id), + 2 => format!("{} ", id), + 3 => format!("{} ", id), + 4 => format!("{} ", id), + 5 => format!("{} ", id), + 6 => format!("{} ", id), + 7 => format!("{} ", id), + 8 => format!("{} ", id), + 9 => format!("{} ", id), + 10 => format!("{} ", id), + 11 => format!("{} ", id), + 12 => format!("{} ", id), + 13 => format!("{} ", id), + _ => format!("{}", id), + } +} + +//---------------------------------------------------------------------------------------------------- Main Ping function +// 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>) -> PingResult { + // Start ping + ping.lock().unwrap().pinging = true; + ping.lock().unwrap().prog = 0.0; + let info = format!("{}", "Creating HTTPS Client"); + 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 = match *ip { + C3POOL => C3pool, + CAKE => Cake, + CAKE_EU => CakeEu, + CAKE_UK => CakeUk, + CAKE_US => CakeUs, + MAJESTICBANK_IS => MajesticBankIs, + MAJESTICBANK_SU => MajesticBankSu, + MONERUJO => Monerujo, + RINO => Rino, + SELSTA_1 => Selsta1, + SELSTA_2 => Selsta2, + SETH => Seth, + SINGAPORE => Singapore, + SUPPORTXMR => SupportXmr, + SUPPORTXMR_IR => SupportXmrIr, + _ => XmrVsBeast, + }; + // 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(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)); + let percent = (100.0 - ping.lock().unwrap().prog) / 2.0; + info!("Ping | {}", info); + ping.lock().unwrap().prog = 100.0; + ping.lock().unwrap().msg = info; + ping.lock().unwrap().pinging = false; + ping.lock().unwrap().pinged = true; + info!("Ping ... OK"); + PingResult { nodes, fastest } +} diff --git a/src/p2pool.rs b/src/p2pool.rs index a07883b..9ddf7c5 100644 --- a/src/p2pool.rs +++ b/src/p2pool.rs @@ -17,26 +17,163 @@ use crate::App; use crate::constants::*; -use crate::state::P2pool; -use crate::node::NodeEnum; -use crate::node::{RINO,SETH,SELSTA}; - -// pub simple: bool, -// pub mini: bool, -// pub out_peers: u8, -// pub in_peers: u8, -// pub log_level: u8, -// pub node: crate::node::NodeEnum, -// pub monerod: String, -// pub rpc: u16, -// pub zmq: u16, -// pub address: String, - +use crate::state::*; +use crate::node::*; +use crate::node::NodeEnum::*; +use std::sync::{Arc,Mutex}; +use std::thread; +use log::*; +use egui::{TextEdit,SelectableLabel,ComboBox,Label}; +use egui::TextStyle::*; +use egui::FontFamily::Proportional; +use egui::{FontId,Button,Color32,RichText}; +use regex::Regex; impl P2pool { - pub fn show(&mut self, width: f32, height: f32, ctx: &egui::Context, ui: &mut egui::Ui) { + pub fn show(&mut self, og: &Arc>, online: bool, ping: &Arc>, addr_regex: &Regex, width: f32, height: f32, ctx: &egui::Context, ui: &mut egui::Ui) { + let text_edit = height / 20.0; + // Console + ui.group(|ui| { + let height = height / SPACE; + let width = width - SPACE; + ui.add_sized([width, height*3.0], TextEdit::multiline(&mut "".to_string())); + ui.add_sized([width, text_edit], TextEdit::hint_text(TextEdit::singleline(&mut "".to_string()), r#"Type a command (e.g "help" or "status") and press Enter"#)); + }); + + let height = ui.available_height(); + // [Simple] + if self.simple { + // [Node] + let height = height / 6.0; + ui.spacing_mut().slider_width = width - 8.0; + ui.spacing_mut().icon_width = width / 25.0; + ui.vertical(|ui| { + ui.horizontal(|ui| { + // [Ping List] + let id = og.lock().unwrap().p2pool.node; + let ip = enum_to_ip(id); + let mut ms = 0; + let mut color = Color32::LIGHT_GRAY; + for data in ping.lock().unwrap().nodes.iter() { + if data.id == id { + ms = data.ms; + color = data.color; + break + } + } + let text = RichText::new(format!("⏺ {}ms | {} | {}", ms, id, ip)).color(color); + ComboBox::from_id_source("nodes").selected_text(RichText::text_style(text, Monospace)).show_ui(ui, |ui| { + for data in ping.lock().unwrap().nodes.iter() { + let ms = crate::node::format_ms(data.ms); + let id = crate::node::format_enum(data.id); + let text = RichText::text_style(RichText::new(format!("⏺ {} | {} | {}", ms, id, data.ip)).color(data.color), Monospace); + ui.selectable_value(&mut og.lock().unwrap().p2pool.node, data.id, text); + } + }); + }); + + ui.add_space(5.0); + + ui.horizontal(|ui| { + let width = (width/2.0)-4.0; + // [Select fastest node] + if ui.add_sized([width, height], Button::new("Select fastest node")).on_hover_text(P2POOL_SELECT_FASTEST).clicked() { + let pinged = ping.lock().unwrap().pinged; + let fastest = ping.lock().unwrap().fastest; + if pinged && og.lock().unwrap().p2pool.node != fastest { + og.lock().unwrap().p2pool.node = ping.lock().unwrap().fastest; + og.lock().unwrap().save(); + } + } + // [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 og_clone = Arc::clone(og); + ping.lock().unwrap().pinging = true; + thread::spawn(move|| { + info!("Spawning ping thread..."); + let ping_result = crate::node::ping(ping.clone()); + ping.lock().unwrap().nodes = ping_result.nodes; + ping.lock().unwrap().fastest = ping_result.fastest; + if og_clone.lock().unwrap().p2pool.auto_select { + og_clone.lock().unwrap().p2pool.node = ping_result.fastest; + og_clone.lock().unwrap().save(); + } + }); + }}); + + ui.vertical(|ui| { + let height = height / 2.0; + let pinging = ping.lock().unwrap().pinging; + ui.set_enabled(pinging); + let prog = ping.lock().unwrap().prog.round(); + let msg = RichText::text_style(RichText::new(format!("{} ... {}%", ping.lock().unwrap().msg, prog)), Monospace); + let height = height / 1.25; + ui.add_space(5.0); + ui.add_sized([width, height], Label::new(msg)); + ui.add_space(5.0); + if pinging { + ui.add_sized([width, height], egui::Spinner::new().size(height)); + } else { + ui.add_sized([width, height], egui::Label::new("...")); + } + ui.add_sized([width, height], egui::ProgressBar::new(prog.round()/100.0)); + ui.add_space(5.0); + }); + }); + + ui.group(|ui| { + ui.horizontal(|ui| { + let width = (width/2.0)-(SPACE*1.5); + // [Auto-node] + [Auto-select] + let mut style = (*ctx.style()).clone(); + style.spacing.icon_width_inner = height/1.5; + style.spacing.icon_width = height; + style.spacing.icon_spacing = 20.0; + ctx.set_style(style); + ui.add_sized([width, height], egui::Checkbox::new(&mut self.auto_select, "Auto-select")).on_hover_text(P2POOL_AUTO_SELECT); + ui.separator(); + ui.add_sized([width, height], egui::Checkbox::new(&mut self.auto_node, "Auto-node")).on_hover_text(P2POOL_AUTO_NODE); + })}); + + // [Address] + let height = ui.available_height(); + ui.horizontal(|ui| { + let width = width / 100.0; + ui.add_sized([width*6.0, height], Label::new("Address")); + if self.address.is_empty() { + ui.add_sized([width, height], Label::new(RichText::new("➖").color(Color32::LIGHT_GRAY))); + } else if self.address.len() == 95 && Regex::is_match(addr_regex, &self.address) { + ui.add_sized([width, height], Label::new(RichText::new("✔").color(Color32::from_rgb(100, 230, 100)))); + } else { + ui.add_sized([width, height], Label::new(RichText::new("❌").color(Color32::from_rgb(230, 50, 50)))); + } + ui.spacing_mut().text_edit_width = (width*9.0)-(SPACE*2.5); + ui.style_mut().override_text_style = Some(Monospace); + ui.add_sized([ui.available_width(), text_edit], TextEdit::hint_text(TextEdit::singleline(&mut self.address), "4...")).on_hover_text(P2POOL_ADDRESS); + }); +// ui.horizontal(|ui| { +// ui.add_sized([width, height/2.0], Label::new("Address:")); +// ui.add_sized([width, height], TextEdit::multiline(&mut self.address)); +// })}); + // [Advanced] + } else { // TODO: // ping code + // If ping was pressed, start thread +// if self.ping { +// self.ping = false; +// self.pinging = Arc::new(Mutex::new(true)); +// let node_clone = Arc::clone(&self.node); +// let pinging_clone = Arc::clone(&self.pinging); +// thread::spawn(move|| { +// let result = NodeStruct::ping(); +// *node_clone.lock().unwrap() = result.nodes; +// *pinging_clone.lock().unwrap() = false; +// }); +// } + // If ping-ING, display stats // if *self.pinging.lock().unwrap() { // egui::CentralPanel::default().show(ctx, |ui| { @@ -58,14 +195,7 @@ impl P2pool { // return // } - let height = ui.available_height() / 10.0; - let mut width = ui.available_width() - 50.0; - ui.group(|ui| { - ui.add_sized([width, height*4.0], egui::TextEdit::multiline(&mut "".to_owned())); - ui.add_sized([width, 30.0], egui::TextEdit::singleline(&mut "".to_owned())); - }); - - width = width - 30.0; + let width = width - 30.0; let mut style = (*ctx.style()).clone(); let height = ui.available_height()/1.2; ui.horizontal(|ui| { @@ -105,7 +235,7 @@ impl P2pool { egui::ComboBox::from_label(self.node.to_string()).selected_text(RINO).show_ui(ui, |ui| { ui.selectable_value(&mut self.node, NodeEnum::Rino, RINO); ui.selectable_value(&mut self.node, NodeEnum::Seth, SETH); - ui.selectable_value(&mut self.node, NodeEnum::Selsta, SELSTA); + ui.selectable_value(&mut self.node, NodeEnum::Selsta1, SELSTA_1); }); // ); }); @@ -138,4 +268,5 @@ impl P2pool { ui.text_edit_singleline(&mut self.address); })}); } + } } diff --git a/src/state.rs b/src/state.rs index d246f63..9fd367d 100644 --- a/src/state.rs +++ b/src/state.rs @@ -61,6 +61,8 @@ impl State { p2pool: P2pool { simple: true, mini: true, + auto_node: true, + auto_select: true, out_peers: 10, in_peers: 10, log_level: 3, @@ -311,6 +313,8 @@ pub struct Gupax { pub struct P2pool { pub simple: bool, pub mini: bool, + pub auto_node: bool, + pub auto_select: bool, pub out_peers: u16, pub in_peers: u16, pub log_level: u8,