From 46b528ecbe46f9db61e00e7e8845761f2560d495 Mon Sep 17 00:00:00 2001 From: hinto-janaiyo Date: Wed, 28 Dec 2022 16:04:26 -0500 Subject: [PATCH] Status Submenu: separate [Regex], [AtomicUnit], [PayoutOrd] This fixes some funcs, tests and separates some structs into separate files. --- README.md | 2 +- src/README.md | 4 +- src/disk.rs | 142 ++++++++++------------- src/helper.rs | 296 ++++++----------------------------------------- src/main.rs | 76 ++++++------- src/regex.rs | 89 +++++++++++++++ src/xmr.rs | 310 ++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 533 insertions(+), 386 deletions(-) create mode 100644 src/regex.rs create mode 100644 src/xmr.rs diff --git a/README.md b/README.md index f281a62..d495d80 100644 --- a/README.md +++ b/README.md @@ -482,7 +482,7 @@ You need [`cargo`](https://www.rust-lang.org/learn/get-started), Rust's build to The `--release` profile in Gupax is set to prefer code performance & small binary sizes over compilation speed (see [`Cargo.toml`](https://github.com/hinto-janaiyo/gupax/blob/main/Cargo.toml)). Gupax itself (with all dependencies already built) takes around 1m30s to build (vs 10s on a normal `--release`) with a Ryzen 5950x. -There are `28` unit tests throughout the codebase files, you should probably run: +There are `30` unit tests throughout the codebase files, you should probably run: ``` cargo test ``` diff --git a/src/README.md b/src/README.md index d1ab7da..dd7a65e 100644 --- a/src/README.md +++ b/src/README.md @@ -18,7 +18,7 @@ ## Structure | File/Folder | Purpose | |--------------|---------| -| constants.rs | General constants needed in Gupax +| constants.rs | General constants used in Gupax | disk.rs | Code for writing to disk: `state.toml/node.toml/pool.toml`; This holds the structs for the [State] struct | ferris.rs | Cute crab bytes | gupax.rs | `Gupax` tab @@ -26,9 +26,11 @@ | main.rs | The main `App` struct that holds all data + misc data/functions | node.rs | Community node ping code for the `P2Pool` simple tab | p2pool.rs | `P2Pool` tab +| regex.rs | General regexes used in Gupax | status.rs | `Status` tab | sudo.rs | Code for handling `sudo` escalation for XMRig on Unix | update.rs | Update code for the `Gupax` tab +| xmr.rs | Code for handling actual XMR, `AtomicUnit` & `PayoutOrd` | xmrig.rs | `XMRig` tab ## Thread Model diff --git a/src/disk.rs b/src/disk.rs index ef00254..1d10b95 100644 --- a/src/disk.rs +++ b/src/disk.rs @@ -46,6 +46,7 @@ use crate::{ constants::*, gupax::Ratio, Tab, + xmr::*, }; use log::*; @@ -78,17 +79,13 @@ pub const POOL_TOML: &str = "pool.toml"; pub const GUPAX_P2POOL_API_DIRECTORY: &str = r"p2pool\"; #[cfg(target_family = "unix")] pub const GUPAX_P2POOL_API_DIRECTORY: &str = "p2pool/"; -pub const GUPAX_P2POOL_API_PAYOUT: &str = "payout"; -pub const GUPAX_P2POOL_API_BLOCK: &str = "block"; -pub const GUPAX_P2POOL_API_TOTAL_PAYOUT: &str = "total_payout"; -pub const GUPAX_P2POOL_API_TOTAL_XMR: &str = "total_xmr"; -pub const GUPAX_P2POOL_API_TOTAL_BLOCK: &str = "total_block"; -pub const GUPAX_P2POOL_API_FILE_ARRAY: [&str; 5] = [ +pub const GUPAX_P2POOL_API_PAYOUT: &str = "payout_log"; +pub const GUPAX_P2POOL_API_TOTAL_PAYOUT: &str = "payout"; +pub const GUPAX_P2POOL_API_TOTAL_XMR: &str = "xmr"; +pub const GUPAX_P2POOL_API_FILE_ARRAY: [&str; 3] = [ GUPAX_P2POOL_API_PAYOUT, - GUPAX_P2POOL_API_BLOCK, GUPAX_P2POOL_API_TOTAL_PAYOUT, GUPAX_P2POOL_API_TOTAL_XMR, - GUPAX_P2POOL_API_TOTAL_BLOCK, ]; #[cfg(target_os = "windows")] @@ -562,21 +559,16 @@ impl Pool { } //---------------------------------------------------------------------------------------------------- Gupax-P2Pool API -#[derive(Clone,Eq,PartialEq,Debug)] +#[derive(Clone,Debug)] pub struct GupaxP2poolApi { pub payout: HumanNumber, - pub xmr: HumanNumber, - pub block: HumanNumber, - pub int_payout: u128, - pub int_xmr: u128, - pub int_block: u128, + pub xmr: AtomicUnit, + pub int_payout: u64, + pub payout_ord: PayoutOrd, // Ordered Vec of payouts pub log_payout: String, - pub log_block: String, + pub path_xmr: PathBuf, pub path_int_payout: PathBuf, - pub path_int_xmr: PathBuf, - pub path_int_block: PathBuf, pub path_log_payout: PathBuf, - pub path_log_block: PathBuf, } impl Default for GupaxP2poolApi { fn default() -> Self { Self::new() } } @@ -585,38 +577,27 @@ impl GupaxP2poolApi { pub fn new() -> Self { Self { payout: HumanNumber::unknown(), - xmr: HumanNumber::unknown(), - block: HumanNumber::unknown(), + xmr: AtomicUnit::new(), int_payout: 0, - int_xmr: 0, - int_block: 0, log_payout: String::new(), - log_block: String::new(), + payout_ord: PayoutOrd::new(), + path_xmr: PathBuf::new(), path_int_payout: PathBuf::new(), - path_int_xmr: PathBuf::new(), - path_int_block: PathBuf::new(), path_log_payout: PathBuf::new(), - path_log_block: PathBuf::new(), } } pub fn fill_paths(&mut self, gupax_p2pool_dir: &PathBuf) { + let mut path_xmr = gupax_p2pool_dir.clone(); let mut path_int_payout = gupax_p2pool_dir.clone(); - let mut path_int_xmr = gupax_p2pool_dir.clone(); - let mut path_int_block = gupax_p2pool_dir.clone(); let mut path_log_payout = gupax_p2pool_dir.clone(); - let mut path_log_block = gupax_p2pool_dir.clone(); path_int_payout.push(GUPAX_P2POOL_API_TOTAL_PAYOUT); - path_int_xmr.push(GUPAX_P2POOL_API_TOTAL_XMR); - path_int_block.push(GUPAX_P2POOL_API_TOTAL_BLOCK); + path_xmr.push(GUPAX_P2POOL_API_TOTAL_XMR); path_log_payout.push(GUPAX_P2POOL_API_PAYOUT); - path_log_block.push(GUPAX_P2POOL_API_BLOCK); *self = Self { path_int_payout, - path_int_xmr, - path_int_block, + path_xmr, path_log_payout, - path_log_block, ..std::mem::take(self) }; } @@ -633,7 +614,7 @@ impl GupaxP2poolApi { match std::fs::File::create(&path) { Ok(mut f) => { match file { - GUPAX_P2POOL_API_TOTAL_PAYOUT|GUPAX_P2POOL_API_TOTAL_XMR|GUPAX_P2POOL_API_TOTAL_BLOCK => f.write_all(b"0")?, + GUPAX_P2POOL_API_TOTAL_PAYOUT|GUPAX_P2POOL_API_TOTAL_XMR => writeln!(f, "0")?, _ => (), } info!("GupaxP2poolApi | [{}] create ... OK", path.display()); @@ -645,50 +626,63 @@ impl GupaxP2poolApi { } pub fn read_all_files_and_update(&mut self) -> Result<(), TomlError> { - let int_payout = match read_to_string(File::IntPayout, &self.path_int_payout)?.as_str().parse::() { + let int_payout = match read_to_string(File::IntPayout, &self.path_int_payout)?.trim().parse::() { Ok(o) => o, Err(e) => { warn!("GupaxP2poolApi | [int_payout] parse error: {}", e); return Err(TomlError::Parse("int_payout")) } }; - let int_xmr = match read_to_string(File::IntXmr, &self.path_int_xmr)?.as_str().parse::() { - Ok(o) => o, - Err(e) => { warn!("GupaxP2poolApi | [int_xmr] parse error: {}", e); return Err(TomlError::Parse("int_xmr")) } + let xmr = match read_to_string(File::IntXmr, &self.path_xmr)?.trim().parse::() { + Ok(o) => AtomicUnit::from_u128(o), + Err(e) => { warn!("GupaxP2poolApi | [xmr] parse error: {}", e); return Err(TomlError::Parse("xmr")) } }; - let int_block = match read_to_string(File::IntBlock, &self.path_int_block)?.as_str().parse::() { - Ok(o) => o, - Err(e) => { warn!("GupaxP2poolApi | [int_block] parse error: {}", e); return Err(TomlError::Parse("int_block")) } - }; - let payout = HumanNumber::from_u128(int_payout); - let xmr = HumanNumber::from_u128(int_xmr); - let block = HumanNumber::from_u128(int_block); + let payout = HumanNumber::from_u64(int_payout); let log_payout = read_to_string(File::LogPayout, &self.path_log_payout)?; - let log_block = read_to_string(File::LogBlock, &self.path_log_block)?; + self.payout_ord.update_from_payout_log(&log_payout); *self = Self { payout, xmr, - block, int_payout, - int_xmr, - int_block, log_payout, - log_block, ..std::mem::take(self) }; Ok(()) } + // Takes raw int and raw log line and appends it. + pub fn add_payout(&mut self, xmr: u128, log_payout_line: &str) { + self.log_payout.push_str(log_payout_line); + self.int_payout += 1; + self.payout = HumanNumber::from_u64(self.int_payout); + self.xmr = self.xmr.add_u128(xmr); + } + pub fn write_to_all_files(&self) -> Result<(), TomlError> { - Self::save(&self.int_payout.to_string(), &self.path_int_payout)?; - Self::save(&self.int_xmr.to_string(), &self.path_int_xmr)?; - Self::save(&self.int_block.to_string(), &self.path_int_block)?; - Self::save(&self.log_payout, &self.path_log_payout)?; - Self::save(&self.log_block, &self.path_log_block)?; + Self::disk_overwrite(&self.int_payout.to_string(), &self.path_int_payout)?; + Self::disk_overwrite(&self.xmr.to_u128().to_string(), &self.path_xmr)?; + Self::disk_append(&self.log_payout, &self.path_log_payout)?; Ok(()) } - pub fn save(string: &str, path: &PathBuf) -> Result<(), TomlError> { - match fs::write(path, string) { - Ok(_) => { info!("GupaxP2poolApi | Save [{}] ... OK", path.display()); Ok(()) }, - Err(e) => { error!("GupaxP2poolApi | Save [{}] ... FAIL: {}", path.display(), e); Err(TomlError::Io(e)) }, + pub fn disk_append(string: &str, path: &PathBuf) -> Result<(), TomlError> { + use std::io::Write; + let mut file = match fs::OpenOptions::new().append(true).open(path) { + Ok(f) => f, + Err(e) => { error!("GupaxP2poolApi | Append [{}] ... FAIL: {}", path.display(), e); return Err(TomlError::Io(e)) }, + }; + match writeln!(file, "{}", string) { + Ok(_) => { debug!("GupaxP2poolApi | Append [{}] ... OK", path.display()); Ok(()) }, + Err(e) => { error!("GupaxP2poolApi | Append [{}] ... FAIL: {}", path.display(), e); Err(TomlError::Io(e)) }, + } + } + + pub fn disk_overwrite(string: &str, path: &PathBuf) -> Result<(), TomlError> { + use std::io::Write; + let mut file = match fs::OpenOptions::new().write(true).open(path) { + Ok(f) => f, + Err(e) => { error!("GupaxP2poolApi | Overwrite [{}] ... FAIL: {}", path.display(), e); return Err(TomlError::Io(e)) }, + }; + match writeln!(file, "{}", string) { + Ok(_) => { debug!("GupaxP2poolApi | Overwrite [{}] ... OK", path.display()); Ok(()) }, + Err(e) => { error!("GupaxP2poolApi | Overwrite [{}] ... FAIL: {}", path.display(), e); Err(TomlError::Io(e)) }, } } } @@ -742,10 +736,8 @@ pub enum File { // Gupax-P2Pool API LogPayout, // payout | Raw log lines of P2Pool payouts received - LogBlock, // block | Raw log lines of actual blocks found via P2Pool IntPayout, // total_payout | Single [u128] representing total payouts IntXmr, // total_xmr | Single [u128] representing total XMR mined in atomic units - IntBlock, // total_block | Single [u128] representing total blocks found } //---------------------------------------------------------------------------------------------------- [Submenu] enum for [Status] tab @@ -1284,6 +1276,7 @@ mod test { #[test] fn create_and_serde_gupax_p2pool_api() { use crate::disk::GupaxP2poolApi; + use crate::xmr::AtomicUnit; // Get API dir, fill paths. let mut api = GupaxP2poolApi::new(); @@ -1292,32 +1285,23 @@ mod test { GupaxP2poolApi::fill_paths(&mut api, &path); println!("{:#?}", api); - // Create and read all files, write some fake data. + // Create, write some fake data. GupaxP2poolApi::create_all_files(&path).unwrap(); - GupaxP2poolApi::read_all_files_and_update(&mut api).unwrap(); + api.log_payout = "NOTICE 2022-01-27 01:30:23.1377 P2Pool You received a payout of 0.000000000001 XMR in block 2642816".to_string(); api.int_payout = 1; - api.int_xmr = 2; - api.int_block = 3; - api.log_payout = "P2Pool You received a payout of 0.000000000001 XMR in block 2642816".to_string(); - api.log_block = "client 127.0.0.1:51111 user asdf found a mainchain block at height 2642816, submitting it".to_string(); + api.xmr = AtomicUnit::from_u128(2); GupaxP2poolApi::write_to_all_files(&api).unwrap(); println!("AFTER WRITE: {:#?}", api); - // Reset internal stats, read file data. - api.int_payout = 0; - api.int_xmr = 0; - api.int_block = 0; - api.log_payout.clear(); - api.log_block.clear(); + // Read GupaxP2poolApi::read_all_files_and_update(&mut api).unwrap(); println!("AFTER READ: {:#?}", api); // Assert that the file read mutated the internal struct correctly. assert_eq!(api.int_payout, 1); - assert_eq!(api.int_xmr, 2); - assert_eq!(api.int_block, 3); - assert_eq!(api.log_payout, "P2Pool You received a payout of 0.000000000001 XMR in block 2642816"); - assert_eq!(api.log_block, "client 127.0.0.1:51111 user asdf found a mainchain block at height 2642816, submitting it"); + assert_eq!(api.xmr.to_u128(), 2); + assert!(!api.payout_ord.is_empty()); + assert!(api.log_payout.contains("2022-01-27 01:30:23.1377 P2Pool You received a payout of 0.000000000001 XMR in block 2642816\n")); } #[test] diff --git a/src/helper.rs b/src/helper.rs index 8bf25a0..ef6db67 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -46,6 +46,9 @@ use crate::{ constants::*, SudoState, human::*, + GupaxP2poolApi, + P2poolRegex, + xmr::*, }; use sysinfo::SystemExt; use serde::{Serialize,Deserialize}; @@ -77,6 +80,7 @@ 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) + pub gupax_p2pool_api: Arc>, // priv_api_p2pool_local: Arc>, // Serde struct(s) for P2Pool's API files priv_api_p2pool_network: Arc>, priv_api_p2pool_pool: Arc>, @@ -223,7 +227,7 @@ impl std::fmt::Display for ProcessName { //---------------------------------------------------------------------------------------------------- [Helper] impl Helper { //---------------------------------------------------------------------------------------------------- General Functions - pub fn new(instant: std::time::Instant, pub_sys: Arc>, p2pool: Arc>, xmrig: Arc>, gui_api_p2pool: Arc>, gui_api_xmrig: Arc>, img_p2pool: Arc>, img_xmrig: Arc>) -> Self { + pub fn new(instant: std::time::Instant, pub_sys: Arc>, p2pool: Arc>, xmrig: Arc>, gui_api_p2pool: Arc>, gui_api_xmrig: Arc>, img_p2pool: Arc>, img_xmrig: Arc>, gupax_p2pool_api: Arc>) -> Self { Self { instant, pub_sys, @@ -241,26 +245,33 @@ impl Helper { gui_api_xmrig, img_p2pool, img_xmrig, + gupax_p2pool_api, } } - // Reads a PTY which combines STDOUT/STDERR for me, yay - fn read_pty(output_parse: Arc>, output_pub: Arc>, reader: Box, name: ProcessName) { + fn read_pty_xmrig(output_parse: Arc>, output_pub: Arc>, reader: Box) { use std::io::BufRead; let mut stdout = std::io::BufReader::new(reader).lines(); // We don't need to write twice for XMRig, since we dont parse it... yet. - if name == ProcessName::Xmrig { - while let Some(Ok(line)) = stdout.next() { -// println!("{}", line); // For debugging. -// if let Err(e) = writeln!(output_parse.lock().unwrap(), "{}", line) { error!("PTY | Output error: {}", e); } - if let Err(e) = writeln!(output_pub.lock().unwrap(), "{}", line) { error!("PTY | Output error: {}", e); } - } - } else { - while let Some(Ok(line)) = stdout.next() { -// println!("{}", line); // For debugging. - if let Err(e) = writeln!(output_parse.lock().unwrap(), "{}", line) { error!("PTY | Output error: {}", e); } - if let Err(e) = writeln!(output_pub.lock().unwrap(), "{}", line) { error!("PTY | Output error: {}", e); } + while let Some(Ok(line)) = stdout.next() { +// println!("{}", line); // For debugging. + if let Err(e) = writeln!(output_pub.lock().unwrap(), "{}", line) { error!("XMRig PTY | Output error: {}", e); } + } + } + + fn read_pty_p2pool(output_parse: Arc>, output_pub: Arc>, reader: Box, regex: P2poolRegex, gupax_p2pool_api: Arc>) { + use std::io::BufRead; + let mut stdout = std::io::BufReader::new(reader).lines(); + while let Some(Ok(line)) = stdout.next() { +// println!("{}", line); // For debugging. + if regex.payout.is_match(&line) { + debug!("P2Pool PTY | Found payout, attempting write: {}", line); + let (date, atomic_unit, block) = PayoutOrd::parse_line(&line, ®ex); + GupaxP2poolApi::add_payout(&mut gupax_p2pool_api.lock().unwrap(), atomic_unit.to_u128(), &line); + if let Err(e) = GupaxP2poolApi::write_to_all_files(&gupax_p2pool_api.lock().unwrap()) { error!("P2Pool PTY GupaxP2poolApi | Write error: {}", e); } } + if let Err(e) = writeln!(output_parse.lock().unwrap(), "{}", line) { error!("P2Pool PTY Parse | Output error: {}", e); } + if let Err(e) = writeln!(output_pub.lock().unwrap(), "{}", line) { error!("P2Pool PTY Pub | Output error: {}", e); } } } @@ -337,9 +348,10 @@ impl Helper { 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 gupax_p2pool_api = Arc::clone(&helper.lock().unwrap().gupax_p2pool_api); let path = path.clone(); thread::spawn(move || { - Self::spawn_p2pool_watchdog(process, gui_api, pub_api, args, path, api_path_local, api_path_network, api_path_pool); + Self::spawn_p2pool_watchdog(process, gui_api, pub_api, args, path, api_path_local, api_path_network, api_path_pool, gupax_p2pool_api); }); } @@ -347,7 +359,7 @@ impl Helper { // 6 characters separated with dots like so: [4abcde...abcdef] fn head_tail_of_monero_address(address: &str) -> String { if address.len() < 95 { return "???".to_string() } - let head = &address[0..5]; + let head = &address[0..6]; let tail = &address[89..95]; head.to_owned() + "..." + tail } @@ -443,7 +455,7 @@ impl Helper { } // 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>, args: Vec, path: std::path::PathBuf, api_path_local: std::path::PathBuf, api_path_network: std::path::PathBuf, api_path_pool: 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, gupax_p2pool_api: Arc>) { // 1a. Create PTY debug!("P2Pool | Creating PTY..."); let pty = portable_pty::native_pty_system(); @@ -473,12 +485,16 @@ impl Helper { lock.stdin = Some(pair.master); drop(lock); + let regex = P2poolRegex::new(); + // 3. Spawn PTY read thread debug!("P2Pool | Spawning PTY read thread..."); let output_parse = Arc::clone(&process.lock().unwrap().output_parse); let output_pub = Arc::clone(&process.lock().unwrap().output_pub); + let gupax_p2pool_api = Arc::clone(&gupax_p2pool_api); + let regex_clone = regex.clone(); thread::spawn(move || { - Self::read_pty(output_parse, output_pub, reader, ProcessName::P2pool); + Self::read_pty_p2pool(output_parse, output_pub, reader, regex_clone, gupax_p2pool_api); }); let output_parse = Arc::clone(&process.lock().unwrap().output_parse); let output_pub = Arc::clone(&process.lock().unwrap().output_pub); @@ -498,7 +514,6 @@ impl Helper { Err(e) => warn!("P2Pool | Creating default empty API file ... FAIL ... {}", e), } } - let regex = P2poolRegex::new(); let start = process.lock().unwrap().start; // Reset stats before loop @@ -875,7 +890,7 @@ impl Helper { let output_parse = Arc::clone(&process.lock().unwrap().output_parse); let output_pub = Arc::clone(&process.lock().unwrap().output_pub); thread::spawn(move || { - Self::read_pty(output_parse, output_pub, reader, ProcessName::Xmrig); + Self::read_pty_xmrig(output_parse, output_pub, reader); }); // We don't parse anything in XMRigs output... yet. // let output_parse = Arc::clone(&process.lock().unwrap().output_parse); @@ -1161,189 +1176,6 @@ impl Helper { } } -//---------------------------------------------------------------------------------------------------- Regexes -// Not to be confused with the [Regexes] struct in [main.rs], this one is meant -// for parsing the output of P2Pool and finding payouts and total XMR found. -// Why Regex instead of the standard library? -// 1. I'm already using Regex -// 2. It's insanely faster -// -// The following STDLIB implementation takes [0.003~] seconds to find all matches given a [String] with 30k lines: -// let mut n = 0; -// for line in P2POOL_OUTPUT.lines() { -// if line.contains("payout of [0-9].[0-9]+ XMR") { n += 1; } -// } -// -// This regex function takes [0.0003~] seconds (10x faster): -// let regex = Regex::new("payout of [0-9].[0-9]+ XMR").unwrap(); -// let n = regex.find_iter(P2POOL_OUTPUT).count(); -// -// Both are nominally fast enough where it doesn't matter too much but meh, why not use regex. -struct P2poolRegex { - payout: regex::Regex, - float: regex::Regex, - date: regex::Regex, - block: regex::Regex, - int: regex::Regex, -} - -impl P2poolRegex { - fn new() -> Self { - Self { - payout: regex::Regex::new("payout of [0-9].[0-9]+ XMR").unwrap(), - float: regex::Regex::new("[0-9].[0-9]+").unwrap(), - date: regex::Regex::new("[0-9]+-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+.[0-9]+").unwrap(), - block: regex::Regex::new("block [0-9]+").unwrap(), - int: regex::Regex::new("[0-9]+").unwrap(), - } - } -} - -//---------------------------------------------------------------------------------------------------- XMR AtomicUnit -#[derive(Debug, Clone)] -struct AtomicUnit(u128); - -impl AtomicUnit { - fn new() -> Self { - Self(0) - } - - fn sum_vec(vec: &Vec) -> Self { - let mut sum = 0; - for int in vec { - sum += int.0; - } - Self(sum) - } - - fn from_f64(f: f64) -> Self { - Self((f * 1_000_000_000_000.0) as u128) - } - - fn to_f64(&self) -> f64 { - self.0 as f64 / 1_000_000_000_000.0 - } - - fn to_human_number_12_point(&self) -> HumanNumber { - let f = self.0 as f64 / 1_000_000_000_000.0; - HumanNumber::from_f64_12_point(f) - } - - fn to_human_number_no_fmt(&self) -> HumanNumber { - let f = self.0 as f64 / 1_000_000_000_000.0; - HumanNumber::from_f64_no_fmt(f) - } -} - -// Displays AtomicUnit as a real XMR floating point. -impl std::fmt::Display for AtomicUnit { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", Self::to_human_number_12_point(self)) - } -} - -//---------------------------------------------------------------------------------------------------- [PayoutOrd] -// This is the struct for ordering P2Pool payout lines into a structured and ordered vector of elements. -// The structure goes as follows: -// -// Vec<(String, AtomicUnit, u64)> -// "2022-08-17 12:16:11.8662" | 0.002382256231 XMR | Block 2573821 -// [0] = DATE -// [1] = XMR IN ATOMIC-UNITS -// [2] = MONERO BLOCK -#[derive(Debug, Clone)] -pub struct PayoutOrd(Vec<(String, AtomicUnit, HumanNumber)>); - -impl PayoutOrd { - fn new() -> Self { - Self(vec![(String::from("????-??-?? ??:??:??.????"), AtomicUnit::new(), HumanNumber::unknown())]) - } - - // Takes in input of ONLY P2Pool payout logs and - // converts it into a usable [PayoutOrd] - // It expects log lines like this: - // "NOTICE 2022-04-11 00:20:17.2571 P2Pool You received a payout of 0.001371623621 XMR in block 2562511" - // For efficiency reasons, I'd like to know the byte size - // we should allocate for the vector so we aren't adding every loop. - // Given a log [str], the equation for how many bytes the final vec will be is: - // (BYTES_OF_DATE + BYTES OF XMR + BYTES OF BLOCK) * amount_of_lines - // The first three are more or less constants (monero block 10m is in 10,379 years...): [23, 14, 7] (sum: 44) - // Add 16 more bytes for wrapper type overhead and it's an even [60] bytes per line. - fn update_from_payout_log(&mut self, log: &str, regex: &P2poolRegex) { - let amount_of_lines = log.lines().count(); - let mut vec: Vec<(String, AtomicUnit, HumanNumber)> = Vec::with_capacity(60 * amount_of_lines); - for line in log.lines() { - // Date - let date = match regex.date.find(line) { - Some(date) => date.as_str().to_string(), - None => { error!("P2Pool | Date parse error: [{}]", line); "????-??-?? ??:??:??.????".to_string() }, - }; - // AtomicUnit - let atomic_unit = if let Some(word) = regex.payout.find(line) { - if let Some(word) = regex.float.find(word.as_str()) { - match word.as_str().parse::() { - Ok(au) => AtomicUnit::from_f64(au), - Err(e) => { error!("P2Pool | AtomicUnit parse error: [{}] on [{}]", e, line); AtomicUnit::new() }, - } - } else { - AtomicUnit::new() - } - } else { - AtomicUnit::new() - }; - // Block - let block = if let Some(word) = regex.block.find(line) { - if let Some(word) = regex.int.find(word.as_str()) { - match word.as_str().parse::() { - Ok(b) => HumanNumber::from_u64(b), - Err(e) => { error!("P2Pool | Block parse error: [{}] on [{}]", e, line); HumanNumber::unknown() }, - } - } else { - HumanNumber::unknown() - } - } else { - HumanNumber::unknown() - }; - vec.push((date, atomic_unit, block)); - } - *self = Self(vec); - } - - // Takes the raw components (no wrapper types), convert them and pushes to existing [Self] - fn push(&mut self, date: String, atomic_unit: u128, block: u64) { - let atomic_unit = AtomicUnit(atomic_unit); - let block = HumanNumber::from_u64(block); - self.0.push((date, atomic_unit, block)); - } - - // Sort [Self] from highest payout to lowest - fn sort_payout_high_to_low(&mut self) { - // This is a little confusing because wrapper types are basically 1 element tuples so: - // self.0 = The [Vec] within [PayoutOrd] - // b.1.0 = [b] is [(String, AtomicUnit, HumanNumber)], [.1] is the [AtomicUnit] inside it, [.0] is the [u128] inside that - // a.1.0 = Same deal, but we compare it with the previous value (b) - self.0.sort_by(|a, b| b.1.0.cmp(&a.1.0)); - } - - fn sort_payout_low_to_high(&mut self) { - self.0.sort_by(|a, b| a.1.0.cmp(&b.1.0)); - } - - // Recent <-> Oldest relies on the line order. - // The raw log lines will be shown instead of this struct. -} - -impl Default for PayoutOrd { fn default() -> Self { Self::new() } } - -impl std::fmt::Display for PayoutOrd { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - for i in &self.0 { - writeln!(f, "{} | {} XMR | Block {}", i.0, i.1, i.2)?; - } - Ok(()) - } -} - //---------------------------------------------------------------------------------------------------- [ImgP2pool] // A static "image" of data that P2Pool started with. // This is just a snapshot of the user data when they initially started P2Pool. @@ -1946,64 +1778,6 @@ struct Result { //---------------------------------------------------------------------------------------------------- TESTS #[cfg(test)] mod test { - #[test] - fn update_p2pool_payout_log() { - use crate::helper::PayoutOrd; - use crate::helper::P2poolRegex; - let log = -r#"NOTICE 2021-12-21 01:01:01.1111 P2Pool You received a payout of 0.001000000000 XMR in block 1 -NOTICE 2021-12-21 02:01:01.1111 P2Pool You received a payout of 0.002000000000 XMR in block 2 -NOTICE 2021-12-21 03:01:01.1111 P2Pool You received a payout of 0.003000000000 XMR in block 3 -"#; - let mut payout_ord = PayoutOrd::new(); - println!("BEFORE: {}", payout_ord); - PayoutOrd::update_from_payout_log(&mut payout_ord, log, &P2poolRegex::new()); - println!("AFTER: {}", payout_ord); - let should_be = -r#"2021-12-21 01:01:01.1111 | 0.001000000000 XMR | Block 1 -2021-12-21 02:01:01.1111 | 0.002000000000 XMR | Block 2 -2021-12-21 03:01:01.1111 | 0.003000000000 XMR | Block 3 -"#; - assert_eq!(payout_ord.to_string(), should_be) - } - - #[test] - fn sort_p2pool_payout_ord() { - use crate::helper::PayoutOrd; - use crate::helper::AtomicUnit; - use crate::helper::HumanNumber; - let mut payout_ord = PayoutOrd(vec![ - ("2022-09-08 18:42:55.4636".to_string(), AtomicUnit(1000000000), HumanNumber::from_u64(2654321)), - ("2022-09-09 16:18:26.7582".to_string(), AtomicUnit(2000000000), HumanNumber::from_u64(2654322)), - ("2022-09-10 11:15:21.1272".to_string(), AtomicUnit(3000000000), HumanNumber::from_u64(2654323)), - ]); - println!("OG: {:#?}", payout_ord); - - // High to Low - PayoutOrd::sort_payout_high_to_low(&mut payout_ord); - println!("AFTER PAYOUT HIGH TO LOW: {:#?}", payout_ord); - let should_be = -r#"2022-09-10 11:15:21.1272 | 0.003000000000 XMR | Block 2,654,323 -2022-09-09 16:18:26.7582 | 0.002000000000 XMR | Block 2,654,322 -2022-09-08 18:42:55.4636 | 0.001000000000 XMR | Block 2,654,321 -"#; - println!("SHOULD_BE:\n{}", should_be); - println!("IS:\n{}", payout_ord); - assert_eq!(payout_ord.to_string(), should_be); - - // Low to High - PayoutOrd::sort_payout_low_to_high(&mut payout_ord); - println!("AFTER PAYOUT LOW TO HIGH: {:#?}", payout_ord); - let should_be = -r#"2022-09-08 18:42:55.4636 | 0.001000000000 XMR | Block 2,654,321 -2022-09-09 16:18:26.7582 | 0.002000000000 XMR | Block 2,654,322 -2022-09-10 11:15:21.1272 | 0.003000000000 XMR | Block 2,654,323 -"#; - println!("SHOULD_BE:\n{}", should_be); - println!("IS:\n{}", payout_ord); - assert_eq!(payout_ord.to_string(), should_be); - } - #[test] fn reset_gui_output() { let max = crate::helper::GUI_OUTPUT_LEEWAY; diff --git a/src/main.rs b/src/main.rs index ae92085..3de27fe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,7 +37,7 @@ use eframe::{egui,NativeOptions}; use log::*; use env_logger::{Builder,WriteStyle}; // Regex -use regex::Regex; +use ::regex::Regex; // Serde use serde::{Serialize,Deserialize}; // std @@ -63,7 +63,9 @@ mod xmrig; mod update; mod helper; mod human; -use {ferris::*,constants::*,node::*,disk::*,update::*,gupax::*,helper::*}; +mod regex; +mod xmr; +use {crate::regex::*,ferris::*,constants::*,node::*,disk::*,update::*,gupax::*,helper::*}; // Sudo (dummy values for Windows) mod sudo; @@ -134,7 +136,7 @@ pub struct App { // This is a file-based API that contains data for permanent stats. // The below struct holds everything needed for it, the paths, the // actual stats, and all the functions needed to mutate them. - gupax_p2pool_api: GupaxP2poolApi, + gupax_p2pool_api: Arc>, // Static stuff pid: sysinfo::Pid, // Gupax's PID max_threads: usize, // Max amount of detected system threads @@ -213,7 +215,7 @@ impl App { restart: Arc::new(Mutex::new(Restart::No)), diff: false, error_state: ErrorState::new(), - helper: Arc::new(Mutex::new(Helper::new(now, pub_sys.clone(), p2pool.clone(), xmrig.clone(), p2pool_api.clone(), xmrig_api.clone(), p2pool_img.clone(), xmrig_img.clone()))), + helper: Arc::new(Mutex::new(Helper::new(now, pub_sys.clone(), p2pool.clone(), xmrig.clone(), p2pool_api.clone(), xmrig_api.clone(), p2pool_img.clone(), xmrig_img.clone(), Arc::new(Mutex::new(GupaxP2poolApi::new()))))), p2pool, xmrig, p2pool_api, @@ -226,7 +228,7 @@ impl App { resizing: false, alpha: 0, no_startup: false, - gupax_p2pool_api: GupaxP2poolApi::new(), + gupax_p2pool_api: Arc::new(Mutex::new(GupaxP2poolApi::new())), pub_sys, pid, max_threads: num_cpus::get(), @@ -346,16 +348,30 @@ impl App { //---------------------------------------------------------------------------------------------------- // Read [GupaxP2poolApi] disk files app.gupax_p2pool_api_path = crate::disk::get_gupax_p2pool_path(&app.os_data_path); - app.gupax_p2pool_api.fill_paths(&app.gupax_p2pool_api_path); - GupaxP2poolApi::create_all_files(&app.gupax_p2pool_api_path); + let mut gupax_p2pool_api = app.gupax_p2pool_api.lock().unwrap(); + gupax_p2pool_api.fill_paths(&app.gupax_p2pool_api_path); + match GupaxP2poolApi::create_all_files(&app.gupax_p2pool_api_path) { + Ok(_) => debug!("App Init | Creating Gupax-P2Pool API files ... OK"), + Err(err) => { + error!("GupaxP2poolApi ... {}", err); + match err { + Io(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit), + Path(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit), + Serialize(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit), + Deserialize(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit), + Format(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit), + Merge(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Error, ErrorButtons::ResetState), + Parse(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit), + }; + }, + } debug!("App Init | Reading Gupax-P2Pool API files..."); - match app.gupax_p2pool_api.read_all_files_and_update() { + match gupax_p2pool_api.read_all_files_and_update() { Ok(_) => { info!( - "GupaxP2poolApi ... Payouts: {} | XMR (atomic-units): {} | Blocks: {}", - app.gupax_p2pool_api.payout, - app.gupax_p2pool_api.xmr, - app.gupax_p2pool_api.block, + "GupaxP2poolApi ... Payouts: {} | XMR (atomic-units): {}", + gupax_p2pool_api.payout, + gupax_p2pool_api.xmr, ); }, Err(err) => { @@ -371,6 +387,8 @@ impl App { }; }, }; + drop(gupax_p2pool_api); + app.helper.lock().unwrap().gupax_p2pool_api = Arc::clone(&app.gupax_p2pool_api); //---------------------------------------------------------------------------------------------------- let mut og = app.og.lock().unwrap(); // Lock [og] @@ -606,33 +624,6 @@ impl Images { } } -//---------------------------------------------------------------------------------------------------- [Regexes] struct -#[derive(Clone, Debug)] -pub struct Regexes { - pub name: Regex, - pub address: Regex, - pub ipv4: Regex, - pub domain: Regex, - pub port: Regex, -} - -impl Regexes { - fn new() -> Self { - Regexes { - name: Regex::new("^[A-Za-z0-9-_.]+( [A-Za-z0-9-_.]+)*$").unwrap(), - address: Regex::new("^4[A-Za-z1-9]+$").unwrap(), // This still needs to check for (l, I, o, 0) - ipv4: Regex::new(r#"^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$"#).unwrap(), - domain: Regex::new(r#"^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})$"#).unwrap(), - port: Regex::new(r#"^([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$"#).unwrap(), - } - } - - // Check if a Monero address is correct. - pub fn addr_ok(&self, address: &str) -> bool { - address.len() == 95 && Regex::is_match(&self.address, address) && !address.contains('0') && !address.contains('O') && !address.contains('l') - } -} - //---------------------------------------------------------------------------------------------------- [Pressed] enum // These represent the keys pressed during the frame. // I could use egui's [Key] but there is no option for @@ -1682,9 +1673,7 @@ XMRig console byte length: {}\n {:#?}\n ------------------------------------------ XMRIG IMAGE ------------------------------------------ {:#?}\n ------------------------------------------- P2POOL GUI API ------------------------------------------ -{:#?}\n ------------------------------------------- XMRIG GUI API ------------------------------------------ +------------------------------------------ GUPAX-P2POOL API ------------------------------------------ {:#?}\n ------------------------------------------ WORKING STATE ------------------------------------------ {:#?}\n @@ -1717,8 +1706,7 @@ XMRig console byte length: {}\n xmrig_gui_len, self.p2pool_img.lock().unwrap(), self.xmrig_img.lock().unwrap(), - self.p2pool_api.lock().unwrap(), - self.xmrig_api.lock().unwrap(), + self.gupax_p2pool_api.lock().unwrap(), self.state, self.og.lock().unwrap(), ); diff --git a/src/regex.rs b/src/regex.rs new file mode 100644 index 0000000..0a09f36 --- /dev/null +++ b/src/regex.rs @@ -0,0 +1,89 @@ +// Gupax - GUI Uniting P2Pool And XMRig +// +// Copyright (c) 2022 hinto-janaiyo +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// Some regexes used throughout Gupax. + +use regex::Regex; + +//---------------------------------------------------------------------------------------------------- [Regexes] struct +// General purpose Regexes, mostly used in the GUI. +#[derive(Clone, Debug)] +pub struct Regexes { + pub name: Regex, + pub address: Regex, + pub ipv4: Regex, + pub domain: Regex, + pub port: Regex, +} + +impl Regexes { + pub fn new() -> Self { + Regexes { + name: Regex::new("^[A-Za-z0-9-_.]+( [A-Za-z0-9-_.]+)*$").unwrap(), + address: Regex::new("^4[A-Za-z1-9]+$").unwrap(), // This still needs to check for (l, I, o, 0) + ipv4: Regex::new(r#"^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$"#).unwrap(), + domain: Regex::new(r#"^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})$"#).unwrap(), + port: Regex::new(r#"^([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$"#).unwrap(), + } + } + + // Check if a Monero address is correct. + // This actually only checks for length & Base58, and doesn't do any checksum validation + // (the last few bytes of a Monero address are a Keccak hash checksum) so some invalid addresses can trick this function. + pub fn addr_ok(&self, address: &str) -> bool { + address.len() == 95 && Regex::is_match(&self.address, address) && !address.contains('0') && !address.contains('O') && !address.contains('l') + } +} + + +//---------------------------------------------------------------------------------------------------- [P2poolRegex] +// Meant for parsing the output of P2Pool and finding payouts and total XMR found. +// Why Regex instead of the standard library? +// 1. I'm already using Regex +// 2. It's insanely faster +// +// The following STDLIB implementation takes [0.003~] seconds to find all matches given a [String] with 30k lines: +// let mut n = 0; +// for line in P2POOL_OUTPUT.lines() { +// if line.contains("payout of [0-9].[0-9]+ XMR") { n += 1; } +// } +// +// This regex function takes [0.0003~] seconds (10x faster): +// let regex = Regex::new("payout of [0-9].[0-9]+ XMR").unwrap(); +// let n = regex.find_iter(P2POOL_OUTPUT).count(); +// +// Both are nominally fast enough where it doesn't matter too much but meh, why not use regex. +#[derive(Clone,Debug)] +pub struct P2poolRegex { + pub payout: regex::Regex, + pub float: regex::Regex, + pub date: regex::Regex, + pub block: regex::Regex, + pub int: regex::Regex, +} + +impl P2poolRegex { + pub fn new() -> Self { + Self { + payout: regex::Regex::new("payout of [0-9].[0-9]+ XMR").unwrap(), + float: regex::Regex::new("[0-9].[0-9]+").unwrap(), + date: regex::Regex::new("[0-9]+-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+.[0-9]+").unwrap(), + block: regex::Regex::new("block [0-9]+").unwrap(), + int: regex::Regex::new("[0-9]+").unwrap(), + } + } +} diff --git a/src/xmr.rs b/src/xmr.rs new file mode 100644 index 0000000..a2d5cd3 --- /dev/null +++ b/src/xmr.rs @@ -0,0 +1,310 @@ +// Gupax - GUI Uniting P2Pool And XMRig +// +// Copyright (c) 2022 hinto-janaiyo +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// This file is for handling actual XMR integers/floats using [AtomicUnit] & [PayoutOrd] +// AtomicUnit is just a wrapper around a [u128] implementing common XMR Atomic Unit functions. +// PayoutOrd is a wrapper around a [Vec] for sorting P2Pool payouts with this type signature: +// "Vec<(String, AtomicUnit, HumanNumber)>" +// These represent: +// "(DATE, ATOMIC_UNIT, MONERO_BLOCK)" + +use crate::{ + human::*, + P2poolRegex, +}; + +use log::*; + +//---------------------------------------------------------------------------------------------------- XMR AtomicUnit +// After I initially wrote this struct, I forgot why I even needed it. +// I get the XMR received as a float, I display it as a float and it wouldn't be +// too bad if I wrote it to disk as a float, but then I realized [.cmp()] doesn't +// work on [f64] and also that Rust makes sorting floats a pain so instead of deleting +// this code and making some float sorter, I might as well use it. +#[derive(Debug, Clone)] +pub struct AtomicUnit(u128); + +impl AtomicUnit { + pub fn new() -> Self { + Self(0) + } + + pub fn from_u128(u: u128) -> Self { + Self(u) + } + + pub fn add_u128(&mut self, u: u128) -> Self { + Self(self.0 + u) + } + + pub fn add_self(&mut self, atomic_unit: &Self) -> Self { + Self(self.0 + atomic_unit.0) + } + + pub fn to_u128(&self) -> u128 { + self.0 + } + + pub fn sum_vec(vec: &Vec) -> Self { + let mut sum = 0; + for int in vec { + sum += int.0; + } + Self(sum) + } + + pub fn from_f64(f: f64) -> Self { + Self((f * 1_000_000_000_000.0) as u128) + } + + pub fn to_f64(&self) -> f64 { + self.0 as f64 / 1_000_000_000_000.0 + } + + pub fn to_human_number_12_point(&self) -> HumanNumber { + let f = self.0 as f64 / 1_000_000_000_000.0; + HumanNumber::from_f64_12_point(f) + } + + pub fn to_human_number_no_fmt(&self) -> HumanNumber { + let f = self.0 as f64 / 1_000_000_000_000.0; + HumanNumber::from_f64_no_fmt(f) + } +} + +// Displays AtomicUnit as a real XMR floating point. +impl std::fmt::Display for AtomicUnit { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", Self::to_human_number_12_point(self)) + } +} + +//---------------------------------------------------------------------------------------------------- [PayoutOrd] +// This is the struct for ordering P2Pool payout lines into a structured and ordered vector of elements. +// The structure goes as follows: +// "Vec<(String, AtomicUnit, HumanNumber)>" +// Which displays as: +// "2022-08-17 12:16:11.8662" | 0.002382256231 XMR | Block 2573821 +// +// [0] = DATE +// [1] = XMR IN ATOMIC-UNITS +// [2] = MONERO BLOCK +#[derive(Debug,Clone)] +pub struct PayoutOrd(Vec<(String, AtomicUnit, HumanNumber)>); + +impl PayoutOrd { + pub fn new() -> Self { + Self(vec![(String::from("????-??-?? ??:??:??.????"), AtomicUnit::new(), HumanNumber::unknown())]) + } + + pub fn from_vec(vec: Vec<(String, AtomicUnit, HumanNumber)>) -> Self { + Self(vec) + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn parse_line(line: &str, regex: &P2poolRegex) -> (String, AtomicUnit, HumanNumber) { + // Date + let date = match regex.date.find(line) { + Some(date) => date.as_str().to_string(), + None => { error!("P2Pool | Date parse error: [{}]", line); "????-??-?? ??:??:??.????".to_string() }, + }; + // AtomicUnit + let atomic_unit = if let Some(word) = regex.payout.find(line) { + if let Some(word) = regex.float.find(word.as_str()) { + match word.as_str().parse::() { + Ok(au) => AtomicUnit::from_f64(au), + Err(e) => { error!("P2Pool | AtomicUnit parse error: [{}] on [{}]", e, line); AtomicUnit::new() }, + } + } else { + AtomicUnit::new() + } + } else { + AtomicUnit::new() + }; + // Block + let block = if let Some(word) = regex.block.find(line) { + if let Some(word) = regex.int.find(word.as_str()) { + match word.as_str().parse::() { + Ok(b) => HumanNumber::from_u64(b), + Err(e) => { error!("P2Pool | Block parse error: [{}] on [{}]", e, line); HumanNumber::unknown() }, + } + } else { + HumanNumber::unknown() + } + } else { + HumanNumber::unknown() + }; + (date, atomic_unit, block) + } + + // Takes in input of ONLY P2Pool payout logs and + // converts it into a usable [PayoutOrd] + // It expects log lines like this: + // "NOTICE 2022-04-11 00:20:17.2571 P2Pool You received a payout of 0.001371623621 XMR in block 2562511" + // For efficiency reasons, I'd like to know the byte size + // we should allocate for the vector so we aren't adding every loop. + // Given a log [str], the equation for how many bytes the final vec will be is: + // (BYTES_OF_DATE + BYTES OF XMR + BYTES OF BLOCK) * amount_of_lines + // The first three are more or less constants (monero block 10m is in 10,379 years...): [23, 14, 7] (sum: 44) + // Add 16 more bytes for wrapper type overhead and it's an even [60] bytes per line. + pub fn update_from_payout_log(&mut self, log: &str) { + let regex = P2poolRegex::new(); + let amount_of_lines = log.lines().count(); + let mut vec: Vec<(String, AtomicUnit, HumanNumber)> = Vec::with_capacity(60 * amount_of_lines); + for line in log.lines() { + debug!("PayoutOrg | Parsing line: [{}]", line); + vec.push(Self::parse_line(line, ®ex)); + } + *self = Self(vec); + } + + // Takes the raw components (no wrapper types), convert them and pushes to existing [Self] + pub fn push(&mut self, date: &str, atomic_unit: u128, block: u64) { + let atomic_unit = AtomicUnit(atomic_unit); + let block = HumanNumber::from_u64(block); + self.0.push((date.to_string(), atomic_unit, block)); + } + + pub fn atomic_unit_sum(&self) -> AtomicUnit { + let mut sum: u128 = 0; + for (_, atomic_unit, _) in &self.0 { + sum += atomic_unit.to_u128(); + } + AtomicUnit::from_u128(sum) + } + + // Sort [Self] from highest payout to lowest + pub fn sort_payout_high_to_low(&mut self) { + // This is a little confusing because wrapper types are basically 1 element tuples so: + // self.0 = The [Vec] within [PayoutOrd] + // b.1.0 = [b] is [(String, AtomicUnit, HumanNumber)], [.1] is the [AtomicUnit] inside it, [.0] is the [u128] inside that + // a.1.0 = Same deal, but we compare it with the previous value (b) + self.0.sort_by(|a, b| b.1.0.cmp(&a.1.0)); + } + + pub fn sort_payout_low_to_high(&mut self) { + self.0.sort_by(|a, b| a.1.0.cmp(&b.1.0)); + } + + // Recent <-> Oldest relies on the line order. + // The raw log lines will be shown instead of this struct. +} + +impl Default for PayoutOrd { fn default() -> Self { Self::new() } } + +impl std::fmt::Display for PayoutOrd { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + for i in &self.0 { + writeln!(f, "{} | {} XMR | Block {}", i.0, i.1, i.2)?; + } + Ok(()) + } +} + +//---------------------------------------------------------------------------------------------------- TESTS +#[cfg(test)] +mod test { + #[test] + fn update_p2pool_payout_log() { + use crate::xmr::PayoutOrd; + let log = +r#"NOTICE 2021-12-21 01:01:01.1111 P2Pool You received a payout of 0.001000000000 XMR in block 1 +NOTICE 2021-12-21 02:01:01.1111 P2Pool You received a payout of 0.002000000000 XMR in block 2 +NOTICE 2021-12-21 03:01:01.1111 P2Pool You received a payout of 0.003000000000 XMR in block 3 +"#; + let mut payout_ord = PayoutOrd::new(); + println!("BEFORE: {}", payout_ord); + PayoutOrd::update_from_payout_log(&mut payout_ord, log); + println!("AFTER: {}", payout_ord); + let should_be = +r#"2021-12-21 01:01:01.1111 | 0.001000000000 XMR | Block 1 +2021-12-21 02:01:01.1111 | 0.002000000000 XMR | Block 2 +2021-12-21 03:01:01.1111 | 0.003000000000 XMR | Block 3 +"#; + assert_eq!(payout_ord.to_string(), should_be) + } + + #[test] + fn push_to_payout_ord() { + use crate::xmr::PayoutOrd; + use crate::xmr::AtomicUnit; + use crate::human::HumanNumber; + let mut payout_ord = PayoutOrd::from_vec(vec![]); + let should_be = "2022-09-08 18:42:55.4636 | 0.000000000001 XMR | Block 2,654,321\n"; + println!("BEFORE: {:#?}", payout_ord); + payout_ord.push("2022-09-08 18:42:55.4636", 1, 2654321); + println!("AFTER: {}", payout_ord); + println!("SHOULD_BE: {}", should_be); + assert_eq!(payout_ord.to_string(), should_be); + } + + #[test] + fn sum_payout_ord_atomic_unit() { + use crate::xmr::PayoutOrd; + use crate::xmr::AtomicUnit; + use crate::human::HumanNumber; + let mut payout_ord = PayoutOrd::from_vec(vec![ + ("2022-09-08 18:42:55.4636".to_string(), AtomicUnit::from_u128(1), HumanNumber::from_u64(2654321)), + ("2022-09-09 16:18:26.7582".to_string(), AtomicUnit::from_u128(1), HumanNumber::from_u64(2654322)), + ("2022-09-10 11:15:21.1272".to_string(), AtomicUnit::from_u128(1), HumanNumber::from_u64(2654323)), + ]); + println!("OG: {:#?}", payout_ord); + let sum = PayoutOrd::atomic_unit_sum(&payout_ord); + println!("SUM: {}", sum.to_u128()); + assert_eq!(sum.to_u128(), 3); + } + + #[test] + fn sort_p2pool_payout_ord() { + use crate::xmr::PayoutOrd; + use crate::xmr::AtomicUnit; + use crate::human::HumanNumber; + let mut payout_ord = PayoutOrd::from_vec(vec![ + ("2022-09-08 18:42:55.4636".to_string(), AtomicUnit::from_u128(1000000000), HumanNumber::from_u64(2654321)), + ("2022-09-09 16:18:26.7582".to_string(), AtomicUnit::from_u128(2000000000), HumanNumber::from_u64(2654322)), + ("2022-09-10 11:15:21.1272".to_string(), AtomicUnit::from_u128(3000000000), HumanNumber::from_u64(2654323)), + ]); + println!("OG: {:#?}", payout_ord); + + // High to Low + PayoutOrd::sort_payout_high_to_low(&mut payout_ord); + println!("AFTER PAYOUT HIGH TO LOW: {:#?}", payout_ord); + let should_be = +r#"2022-09-10 11:15:21.1272 | 0.003000000000 XMR | Block 2,654,323 +2022-09-09 16:18:26.7582 | 0.002000000000 XMR | Block 2,654,322 +2022-09-08 18:42:55.4636 | 0.001000000000 XMR | Block 2,654,321 +"#; + println!("SHOULD_BE:\n{}", should_be); + println!("IS:\n{}", payout_ord); + assert_eq!(payout_ord.to_string(), should_be); + + // Low to High + PayoutOrd::sort_payout_low_to_high(&mut payout_ord); + println!("AFTER PAYOUT LOW TO HIGH: {:#?}", payout_ord); + let should_be = +r#"2022-09-08 18:42:55.4636 | 0.001000000000 XMR | Block 2,654,321 +2022-09-09 16:18:26.7582 | 0.002000000000 XMR | Block 2,654,322 +2022-09-10 11:15:21.1272 | 0.003000000000 XMR | Block 2,654,323 +"#; + println!("SHOULD_BE:\n{}", should_be); + println!("IS:\n{}", payout_ord); + assert_eq!(payout_ord.to_string(), should_be); + } +}