Status Submenu: separate [Regex], [AtomicUnit], [PayoutOrd]

This fixes some funcs, tests and separates some structs into separate files.
This commit is contained in:
hinto-janaiyo 2022-12-28 16:04:26 -05:00
parent fd6398fb4d
commit 46b528ecbe
No known key found for this signature in database
GPG key ID: B1C5A64B80691E45
7 changed files with 533 additions and 386 deletions

View file

@ -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
```

View file

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

View file

@ -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::<u128>() {
let int_payout = match read_to_string(File::IntPayout, &self.path_int_payout)?.trim().parse::<u64>() {
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::<u128>() {
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::<u128>() {
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::<u128>() {
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]

View file

@ -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<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)
pub gupax_p2pool_api: Arc<Mutex<GupaxP2poolApi>>, //
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>>,
@ -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<Mutex<Sys>>, p2pool: Arc<Mutex<Process>>, xmrig: Arc<Mutex<Process>>, gui_api_p2pool: Arc<Mutex<PubP2poolApi>>, gui_api_xmrig: Arc<Mutex<PubXmrigApi>>, img_p2pool: Arc<Mutex<ImgP2pool>>, img_xmrig: Arc<Mutex<ImgXmrig>>) -> Self {
pub fn new(instant: std::time::Instant, pub_sys: Arc<Mutex<Sys>>, p2pool: Arc<Mutex<Process>>, xmrig: Arc<Mutex<Process>>, gui_api_p2pool: Arc<Mutex<PubP2poolApi>>, gui_api_xmrig: Arc<Mutex<PubXmrigApi>>, img_p2pool: Arc<Mutex<ImgP2pool>>, img_xmrig: Arc<Mutex<ImgXmrig>>, gupax_p2pool_api: Arc<Mutex<GupaxP2poolApi>>) -> 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<Mutex<String>>, output_pub: Arc<Mutex<String>>, reader: Box<dyn std::io::Read + Send>, name: ProcessName) {
fn read_pty_xmrig(output_parse: Arc<Mutex<String>>, output_pub: Arc<Mutex<String>>, reader: Box<dyn std::io::Read + Send>) {
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<Mutex<String>>, output_pub: Arc<Mutex<String>>, reader: Box<dyn std::io::Read + Send>, regex: P2poolRegex, gupax_p2pool_api: Arc<Mutex<GupaxP2poolApi>>) {
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, &regex);
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<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) {
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, gupax_p2pool_api: Arc<Mutex<GupaxP2poolApi>>) {
// 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>) -> 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::<f64>() {
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::<u64>() {
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;

View file

@ -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<Mutex<GupaxP2poolApi>>,
// 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(),
);

89
src/regex.rs Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
// 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(),
}
}
}

310
src/xmr.rs Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
// 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>) -> 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::<f64>() {
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::<u64>() {
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, &regex));
}
*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);
}
}