Status Submenu: add serde for p2pools [local/network/pool] API

This commit is contained in:
hinto-janaiyo 2022-12-26 12:18:57 -05:00
parent acba71f157
commit 9245cc8a11
No known key found for this signature in database
GPG key ID: B1C5A64B80691E45
3 changed files with 177 additions and 52 deletions

View file

@ -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).

View file

@ -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";

View file

@ -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 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 Local API | Could not deserialize API data: {}", 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> {
//---------------------------------------------------------------------------------------------------- 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 API | Could not deserialize API data: {}", e); Err(e) },
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 =