From e389385da649be956bae53acc051f0e22b4ee2c7 Mon Sep 17 00:00:00 2001
From: hinto-janaiyo <hinto.janaiyo@protonmail.com>
Date: Mon, 26 Dec 2022 12:18:57 -0500
Subject: [PATCH] Status Submenu: add serde for p2pools [local/network/pool]
 API

---
 src/README.md    |  28 ++++++-
 src/constants.rs |  16 +++-
 src/helper.rs    | 185 +++++++++++++++++++++++++++++++++++------------
 3 files changed, 177 insertions(+), 52 deletions(-)

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<Mutex<ImgXmrig>>,          // A static "image" of the data XMRig started with
 	pub_api_p2pool: Arc<Mutex<PubP2poolApi>>,     // P2Pool API state (for Helper/P2Pool thread)
 	pub_api_xmrig: Arc<Mutex<PubXmrigApi>>,       // XMRig API state (for Helper/XMRig thread)
-	priv_api_p2pool: Arc<Mutex<PrivP2poolApi>>,   // For "watchdog" thread
-	priv_api_xmrig: Arc<Mutex<PrivXmrigApi>>,     // For "watchdog" thread
+	priv_api_p2pool_local: Arc<Mutex<PrivP2poolLocalApi>>,      // Serde struct(s) for P2Pool's API files
+	priv_api_p2pool_network: Arc<Mutex<PrivP2poolNetworkApi>>,
+	priv_api_p2pool_pool: Arc<Mutex<PrivP2poolPoolApi>>,
+	priv_api_xmrig: Arc<Mutex<PrivXmrigApi>>, // 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<String, std::io::Error> {
+		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<Mutex<Self>>) {
@@ -313,19 +320,24 @@ impl Helper {
 	pub fn start_p2pool(helper: &Arc<Mutex<Self>>, 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<Mutex<Self>>, state: &crate::disk::P2pool, path: &std::path::PathBuf) -> (Vec<String>, PathBuf) {
+	pub fn build_p2pool_args_and_mutate_img(helper: &Arc<Mutex<Self>>, state: &crate::disk::P2pool, path: &std::path::PathBuf) -> (Vec<String>, 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<Mutex<Process>>, gui_api: Arc<Mutex<PubP2poolApi>>, pub_api: Arc<Mutex<PubP2poolApi>>, _priv_api: Arc<Mutex<PrivP2poolApi>>, args: Vec<String>, path: std::path::PathBuf, api_path: std::path::PathBuf) {
+	fn spawn_p2pool_watchdog(process: Arc<Mutex<Process>>, gui_api: Arc<Mutex<PubP2poolApi>>, pub_api: Arc<Mutex<PubP2poolApi>>, args: Vec<String>, 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(), &regex);
 
-			// 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<Mutex<Self>>, private: PrivP2poolApi) {
+	// Mutate [PubP2poolApi] with data from a [PrivP2poolLocalApi] and the process output.
+	fn update_from_local(public: &Arc<Mutex<Self>>, 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<String, std::io::Error> {
-		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<Self, serde_json::Error> {
+	fn from_str(string: &str) -> std::result::Result<Self, serde_json::Error> {
 		match serde_json::from_str::<Self>(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<Self, serde_json::Error> {
+		match serde_json::from_str::<Self>(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<Self, serde_json::Error> {
+		match serde_json::from_str::<Self>(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 =