diff --git a/src/README.md b/src/README.md index 98d1701..d1ab7da 100644 --- a/src/README.md +++ b/src/README.md @@ -4,6 +4,7 @@ * [Bootstrap](#Bootstrap) * [Scale](#Scale) * [Naming Scheme](#naming-scheme) +* [Mining Stat Reference](#mining-stat-reference) * [Sudo](#Sudo) * [Why does Gupax need to be Admin? (on Windows)](#why-does-gupax-need-to-be-admin-on-windows) - [The issue](#the-issue) @@ -124,6 +125,31 @@ Exceptions (there are always exceptions...): - XMRig separates the hash and signature - P2Pool hashes are in UPPERCASE +## Mining Stat Reference +Some pseudo JSON for constants/equations needed for generating mining stats. They're here for easy reference, I was never good at math :) +``` +block_time_in_seconds: { + P2POOL_BLOCK_TIME: 10, + MONERO_BLOCK_TIME: 120, +} + +difficulty: { + P2POOL_DIFFICULTY: (current_p2pool_hashrate * P2POOL_BLOCK_TIME), + MONERO_DIFFICULTY: (current_monero_hashrate * MONERO_BLOCK_TIME), +} + +hashrate_per_second: { + P2POOL_HASHRATE: (P2POOL_DIFFICULTY / P2POOL_BLOCK_TIME), + MONERO_HASHRATE: (MONERO_DIFFICULTY / MONERO_BLOCK_TIME), +} + +mean_in_seconds: { + P2POOL_BLOCK_MEAN: (MONERO_DIFF / P2POOL_HASHRATE), + MY_SOLO_BLOCK_MEAN: (MONERO_DIFF / my_hashrate), + MY_P2POOL_SHARE_MEAN: (P2POOL_DIFF / my_hashrate), +} +``` + ## Sudo Unlike Windows, Unix (macOS/Linux) has a userland program that handles all the dirty details of privilege escalation: `sudo`. @@ -208,7 +234,7 @@ This was the solution I would have gone with, but alas, the abstracted `Command` --- ### Windows vs Unix -Unix (macOS/Linux) has have a super nice, easy, friendly, not-completely-garbage userland program called: `sudo`. It is so extremely simple to use `sudo` as a sort of wrapper around XMRig since `sudo` isn't completely backwards and actually has valuable flags! No legacy `Administrator`, no UAC prompt, no shells within shells, no low-level system APIs, no messing with the user Registry. +Unix (macOS/Linux) has a super nice, easy, friendly, not-completely-garbage userland program called: `sudo`. It is so extremely simple to use `sudo` as a sort of wrapper around XMRig since `sudo` isn't completely backwards and actually has valuable flags! No legacy `Administrator`, no UAC prompt, no shells within shells, no low-level system APIs, no messing with the user Registry. You get the user's password, you input it to `sudo` with `--stdin` and you execute XMRig with it. Simple, easy, nice. (Don't forget to zero the password memory, though). diff --git a/src/constants.rs b/src/constants.rs index 1f9981a..295dbb2 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -70,9 +70,17 @@ r#"*---------------------------------------* *---------------------------------------*"#; // P2Pool & XMRig default API stuff #[cfg(target_os = "windows")] -pub const P2POOL_API_PATH: &str = r"local\stats"; // The default relative FS path of P2Pool's local API +pub const P2POOL_API_PATH_LOCAL: &str = r"local\stats"; +#[cfg(target_os = "windows")] +pub const P2POOL_API_PATH_NETWORK: &str = r"network\stats"; +#[cfg(target_os = "windows")] +pub const P2POOL_API_PATH_POOL: &str = r"pool\stats"; #[cfg(target_family = "unix")] -pub const P2POOL_API_PATH: &str = "local/stats"; +pub const P2POOL_API_PATH_LOCAL: &str = "local/stats"; +#[cfg(target_family = "unix")] +pub const P2POOL_API_PATH_NETWORK: &str = "network/stats"; +#[cfg(target_family = "unix")] +pub const P2POOL_API_PATH_POOL: &str = "pool/stats"; pub const XMRIG_API_URI: &str = "1/summary"; // The default relative URI of XMRig's API // Process state tooltips (online, offline, etc) @@ -148,8 +156,8 @@ pub const STATUS_GUPAX_SYSTEM_MEMORY: &str = "How much memory your entire system pub const STATUS_GUPAX_SYSTEM_CPU_MODEL: &str = "The detected model of your system's CPU and its current frequency"; //-- pub const STATUS_P2POOL_UPTIME: &str = "How long P2Pool has been online"; -pub const STATUS_P2POOL_PAYOUTS: &str = "The total amount of payouts received and an extrapolated estimate of how many you will receive. Warning: these stats will be quite inaccurate if your P2Pool hasn't been running for a long time!"; -pub const STATUS_P2POOL_XMR: &str = "The total amount of XMR mined via P2Pool and an extrapolated estimate of how many you will mine in the future. Warning: these stats will be quite inaccurate if your P2Pool hasn't been running for a long time!"; +pub const STATUS_P2POOL_PAYOUTS: &str = "The total amount of payouts received in this instance of P2Pool and an extrapolated estimate of how many you will receive. Warning: these stats will be quite inaccurate if your P2Pool hasn't been running for a long time!"; +pub const STATUS_P2POOL_XMR: &str = "The total amount of XMR mined in this instance of P2Pool and an extrapolated estimate of how many you will mine in the future. Warning: these stats will be quite inaccurate if your P2Pool hasn't been running for a long time!"; pub const STATUS_P2POOL_HASHRATE: &str = "The total amount of hashrate your P2Pool has pointed at it in 15 minute, 1 hour, and 24 hour averages"; pub const STATUS_P2POOL_SHARES: &str = "The total amount of shares found on P2Pool"; pub const STATUS_P2POOL_EFFORT: &str = "The average amount of effort needed to find a share, and the current effort"; diff --git a/src/helper.rs b/src/helper.rs index bdfadc3..4351a6d 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -75,8 +75,10 @@ pub struct Helper { pub img_xmrig: Arc>, // A static "image" of the data XMRig started with pub_api_p2pool: Arc>, // P2Pool API state (for Helper/P2Pool thread) pub_api_xmrig: Arc>, // XMRig API state (for Helper/XMRig thread) - priv_api_p2pool: Arc>, // For "watchdog" thread - priv_api_xmrig: Arc>, // For "watchdog" thread + priv_api_p2pool_local: Arc>, // Serde struct(s) for P2Pool's API files + priv_api_p2pool_network: Arc>, + priv_api_p2pool_pool: Arc>, + priv_api_xmrig: Arc>, // Serde struct for XMRig's HTTP API } // The communication between the data here and the GUI thread goes as follows: @@ -110,12 +112,7 @@ impl Sys { } } } - -impl Default for Sys { - fn default() -> Self { - Self::new() - } -} +impl Default for Sys { fn default() -> Self { Self::new() } } //---------------------------------------------------------------------------------------------------- [Process] Struct // This holds all the state of a (child) process. @@ -229,7 +226,9 @@ impl Helper { instant, pub_sys, uptime: HumanTime::into_human(instant.elapsed()), - priv_api_p2pool: Arc::new(Mutex::new(PrivP2poolApi::new())), + priv_api_p2pool_local: Arc::new(Mutex::new(PrivP2poolLocalApi::new())), + priv_api_p2pool_network: Arc::new(Mutex::new(PrivP2poolNetworkApi::new())), + priv_api_p2pool_pool: Arc::new(Mutex::new(PrivP2poolPoolApi::new())), priv_api_xmrig: Arc::new(Mutex::new(PrivXmrigApi::new())), pub_api_p2pool: Arc::new(Mutex::new(PubP2poolApi::new())), pub_api_xmrig: Arc::new(Mutex::new(PubXmrigApi::new())), @@ -278,6 +277,14 @@ impl Helper { } } + // Read P2Pool/XMRig's API file to a [String]. + fn path_to_string(path: &std::path::PathBuf, name: ProcessName) -> std::result::Result { + match std::fs::read_to_string(path) { + Ok(s) => Ok(s), + Err(e) => { warn!("{} API | [{}] read error: {}", name, path.display(), e); Err(e) }, + } + } + //---------------------------------------------------------------------------------------------------- P2Pool specific // Just sets some signals for the watchdog thread to pick up on. pub fn stop_p2pool(helper: &Arc>) { @@ -313,19 +320,24 @@ impl Helper { pub fn start_p2pool(helper: &Arc>, state: &crate::disk::P2pool, path: &std::path::PathBuf) { helper.lock().unwrap().p2pool.lock().unwrap().state = ProcessState::Middle; - let (args, api_path) = Self::build_p2pool_args_and_mutate_img(helper, state, path); + let (args, api_path_local, api_path_network, api_path_pool) = Self::build_p2pool_args_and_mutate_img(helper, state, path); // Print arguments & user settings to console - crate::disk::print_dash(&format!("P2Pool | Launch arguments: {:#?} | API Path: {:#?}", args, api_path)); + crate::disk::print_dash(&format!( + "P2Pool | Launch arguments: {:#?} | Local API Path: {:#?} | Network API Path: {:#?} | Pool API Path: {:#?}", + args, + api_path_local, + api_path_network, + api_path_pool, + )); // Spawn watchdog thread let process = Arc::clone(&helper.lock().unwrap().p2pool); let gui_api = Arc::clone(&helper.lock().unwrap().gui_api_p2pool); let pub_api = Arc::clone(&helper.lock().unwrap().pub_api_p2pool); - let priv_api = Arc::clone(&helper.lock().unwrap().priv_api_p2pool); let path = path.clone(); thread::spawn(move || { - Self::spawn_p2pool_watchdog(process, gui_api, pub_api, priv_api, args, path, api_path); + Self::spawn_p2pool_watchdog(process, gui_api, pub_api, args, path, api_path_local, api_path_network, api_path_pool); }); } @@ -341,7 +353,7 @@ impl Helper { // Takes in some [State/P2pool] and parses it to build the actual command arguments. // Returns the [Vec] of actual arguments, and mutates the [ImgP2pool] for the main GUI thread // It returns a value... and mutates a deeply nested passed argument... this is some pretty bad code... - pub fn build_p2pool_args_and_mutate_img(helper: &Arc>, state: &crate::disk::P2pool, path: &std::path::PathBuf) -> (Vec, PathBuf) { + pub fn build_p2pool_args_and_mutate_img(helper: &Arc>, state: &crate::disk::P2pool, path: &std::path::PathBuf) -> (Vec, PathBuf, PathBuf, PathBuf) { let mut args = Vec::with_capacity(500); let path = path.clone(); let mut api_path = path; @@ -419,12 +431,17 @@ impl Helper { }; } } - api_path.push(P2POOL_API_PATH); - (args, api_path) + let mut api_path_local = api_path.clone(); + let mut api_path_network = api_path.clone(); + let mut api_path_pool = api_path.clone(); + api_path_local.push(P2POOL_API_PATH_LOCAL); + api_path_network.push(P2POOL_API_PATH_NETWORK); + api_path_pool.push(P2POOL_API_PATH_POOL); + (args, api_path_local, api_path_network, api_path_pool) } // The P2Pool watchdog. Spawns 1 OS thread for reading a PTY (STDOUT+STDERR), and combines the [Child] with a PTY so STDIN actually works. - fn spawn_p2pool_watchdog(process: Arc>, gui_api: Arc>, pub_api: Arc>, _priv_api: Arc>, args: Vec, path: std::path::PathBuf, api_path: std::path::PathBuf) { + fn spawn_p2pool_watchdog(process: Arc>, gui_api: Arc>, pub_api: Arc>, args: Vec, path: std::path::PathBuf, api_path_local: std::path::PathBuf, api_path_network: std::path::PathBuf, api_path_pool: std::path::PathBuf) { // 1a. Create PTY debug!("P2Pool | Creating PTY..."); let pty = portable_pty::native_pty_system(); @@ -464,17 +481,17 @@ impl Helper { let output_parse = Arc::clone(&process.lock().unwrap().output_parse); let output_pub = Arc::clone(&process.lock().unwrap().output_pub); - debug!("P2Pool | Cleaning old API files..."); + debug!("P2Pool | Cleaning old [local] API files..."); // Attempt to remove stale API file - match std::fs::remove_file(&api_path) { + match std::fs::remove_file(&api_path_local) { Ok(_) => info!("P2Pool | Attempting to remove stale API file ... OK"), Err(e) => warn!("P2Pool | Attempting to remove stale API file ... FAIL ... {}", e), } // Attempt to create a default empty one. use std::io::Write; - if std::fs::File::create(&api_path).is_ok() { + if std::fs::File::create(&api_path_local).is_ok() { let text = r#"{"hashrate_15m":0,"hashrate_1h":0,"hashrate_24h":0,"shares_found":0,"average_effort":0.0,"current_effort":0.0,"connections":0}"#; - match std::fs::write(&api_path, text) { + match std::fs::write(&api_path_local, text) { Ok(_) => info!("P2Pool | Creating default empty API file ... OK"), Err(e) => warn!("P2Pool | Creating default empty API file ... FAIL ... {}", e), } @@ -600,13 +617,28 @@ impl Helper { debug!("P2Pool Watchdog | Starting [update_from_output()]"); PubP2poolApi::update_from_output(&pub_api, &output_parse, &output_pub, start.elapsed(), ®ex); - // Read API file into string - debug!("P2Pool Watchdog | Attempting API file read"); - if let Ok(string) = PrivP2poolApi::read_p2pool_api(&api_path) { + // Read [local] API + debug!("P2Pool Watchdog | Attempting [local] API file read"); + if let Ok(string) = Self::path_to_string(&api_path_local, ProcessName::P2pool) { // Deserialize - if let Ok(s) = PrivP2poolApi::str_to_priv_p2pool_api(&string) { + if let Ok(s) = PrivP2poolLocalApi::from_str(&string) { // Update the structs. - PubP2poolApi::update_from_priv(&pub_api, s); + PubP2poolApi::update_from_local(&pub_api, s); + } + } + // If more than 1 minute has passed, read the other API files. + if now.elapsed().as_secs() >= 60 { + debug!("P2Pool Watchdog | Attempting [network] API file read"); + if let Ok(string) = Self::path_to_string(&api_path_network, ProcessName::P2pool) { + if let Ok(s) = PrivP2poolNetworkApi::from_str(&string) { + // PubP2poolApi::update_from_network(&pub_api, s); + } + } + debug!("P2Pool Watchdog | Attempting [pool] API file read"); + if let Ok(string) = Self::path_to_string(&api_path_pool, ProcessName::P2pool) { + if let Ok(s) = PrivP2poolPoolApi::from_str(&string) { + // PubP2poolApi::update_from_network(&pub_api, s); + } } } @@ -1388,7 +1420,7 @@ pub struct PubP2poolApi { pub xmr_hour: f64, pub xmr_day: f64, pub xmr_month: f64, - // The rest are serialized from the API, then turned into [HumanNumber]s + // Local API pub hashrate_15m: HumanNumber, pub hashrate_1h: HumanNumber, pub hashrate_24h: HumanNumber, @@ -1396,6 +1428,8 @@ pub struct PubP2poolApi { pub average_effort: HumanNumber, pub current_effort: HumanNumber, pub connections: HumanNumber, + // Network API + // Pool API } impl Default for PubP2poolApi { @@ -1498,8 +1532,8 @@ impl PubP2poolApi { }; } - // Mutate [PubP2poolApi] with data from a [PrivP2poolApi] and the process output. - fn update_from_priv(public: &Arc>, private: PrivP2poolApi) { + // Mutate [PubP2poolApi] with data from a [PrivP2poolLocalApi] and the process output. + fn update_from_local(public: &Arc>, private: PrivP2poolLocalApi) { // priv -> pub conversion let mut public = public.lock().unwrap(); *public = Self { @@ -1530,12 +1564,11 @@ impl PubP2poolApi { } } -//---------------------------------------------------------------------------------------------------- Private P2Pool API -// This is the data the "watchdog" threads mutate. -// It matches directly to P2Pool's [local/stats] JSON API file (excluding a few stats). +//---------------------------------------------------------------------------------------------------- Private P2Pool "Local" Api +// This matches directly to P2Pool's [local/stats] JSON API file (excluding a few stats). // P2Pool seems to initialize all stats at 0 (or 0.0), so no [Option] wrapper seems needed. #[derive(Debug, Serialize, Deserialize, Clone, Copy)] -struct PrivP2poolApi { +struct PrivP2poolLocalApi { hashrate_15m: u128, hashrate_1h: u128, hashrate_24h: u128, @@ -1545,7 +1578,9 @@ struct PrivP2poolApi { connections: u16, // No one will have more than 65535 connections... right? } -impl PrivP2poolApi { +impl Default for PrivP2poolLocalApi { fn default() -> Self { Self::new() } } + +impl PrivP2poolLocalApi { fn new() -> Self { Self { hashrate_15m: 0, @@ -1558,23 +1593,79 @@ impl PrivP2poolApi { } } - // Read P2Pool's API file to a [String]. - fn read_p2pool_api(path: &std::path::PathBuf) -> std::result::Result { - match std::fs::read_to_string(path) { - Ok(s) => Ok(s), - Err(e) => { warn!("P2Pool API | [{}] read error: {}", path.display(), e); Err(e) }, - } - } - // Deserialize the above [String] into a [PrivP2poolApi] - fn str_to_priv_p2pool_api(string: &str) -> std::result::Result { + fn from_str(string: &str) -> std::result::Result { match serde_json::from_str::(string) { Ok(a) => Ok(a), - Err(e) => { warn!("P2Pool API | Could not deserialize API data: {}", e); Err(e) }, + Err(e) => { warn!("P2Pool Local API | Could not deserialize API data: {}", e); Err(e) }, } } } +//---------------------------------------------------------------------------------------------------- Private P2Pool "Network" API +// This matches P2Pool's [network/stats] JSON API file. +#[derive(Debug, Serialize, Deserialize, Clone)] +struct PrivP2poolNetworkApi { + difficulty: u128, + hash: String, + height: u32, + reward: u128, + timestamp: u32, +} + +impl Default for PrivP2poolNetworkApi { fn default() -> Self { Self::new() } } + +impl PrivP2poolNetworkApi { + fn new() -> Self { + Self { + difficulty: 0, + hash: String::from("???"), + height: 0, + reward: 0, + timestamp: 0, + } + } + + fn from_str(string: &str) -> std::result::Result { + match serde_json::from_str::(string) { + Ok(a) => Ok(a), + Err(e) => { warn!("P2Pool Network API | Could not deserialize API data: {}", e); Err(e) }, + } + } +} + +//---------------------------------------------------------------------------------------------------- Private P2Pool "Pool" API +// This matches P2Pool's [pool/stats] JSON API file. +#[derive(Debug, Serialize, Deserialize, Clone, Copy)] +struct PrivP2poolPoolApi { + pool_statistics: PoolStatistics, +} + +impl Default for PrivP2poolPoolApi { fn default() -> Self { Self::new() } } + +impl PrivP2poolPoolApi { + fn new() -> Self { + Self { + pool_statistics: PoolStatistics::new(), + } + } + + fn from_str(string: &str) -> std::result::Result { + match serde_json::from_str::(string) { + Ok(a) => Ok(a), + Err(e) => { warn!("P2Pool Pool API | Could not deserialize API data: {}", e); Err(e) }, + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, Copy)] +struct PoolStatistics { + hashrate: u128, + miners: u32, +} +impl Default for PoolStatistics { fn default() -> Self { Self::new() } } +impl PoolStatistics { fn new() -> Self { Self { hashrate: 0, miners: 0 } } } + //---------------------------------------------------------------------------------------------------- [ImgXmrig] #[derive(Debug, Clone)] pub struct ImgXmrig { @@ -1928,7 +2019,7 @@ mod test { } #[test] - fn serde_priv_p2pool_api() { + fn serde_priv_p2pool_local_api() { let data = r#"{ "hashrate_15m": 12, @@ -1941,8 +2032,8 @@ mod test { "connections": 123, "incoming_connections": 96 }"#; - use crate::helper::PrivP2poolApi; - let priv_api = PrivP2poolApi::str_to_priv_p2pool_api(data).unwrap(); + use crate::helper::PrivP2poolLocalApi; + let priv_api = PrivP2poolLocalApi::from_str(data).unwrap(); let json = serde_json::ser::to_string_pretty(&priv_api).unwrap(); println!("{}", json); let data_after_ser =