gupaxx/src/xmr.rs

411 lines
15 KiB
Rust

// 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 [u64] 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.
// [u64] can hold max: 18_446_744_073_709_551_615 which equals to 18,446,744,073 XMR (18 billion).
// Given the constant XMR tail emission of (0.3 per minute|18 per hour|432 per day|157,680 per year)
// this would take: 116,976~ years to overflow.
#[derive(Debug,Clone,Copy,PartialEq,Eq)]
pub struct AtomicUnit(u64);
impl AtomicUnit {
pub const fn new() -> Self {
Self(0)
}
pub const fn from_u64(u: u64) -> Self {
Self(u)
}
pub const fn add_u64(self, u: u64) -> Self {
Self(self.0 + u)
}
pub const fn add_self(self, atomic_unit: Self) -> Self {
Self(self.0 + atomic_unit.0)
}
pub const fn to_u64(self) -> u64 {
self.0
}
pub fn to_string(self) -> String {
self.0.to_string()
}
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 u64)
}
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 const fn from_vec(vec: Vec<(String, AtomicUnit, HumanNumber)>) -> Self {
Self(vec)
}
pub fn is_same(a: &Self, b: &Self) -> bool {
if a.0.is_empty() && b.0.is_empty() { return true }
if a.0.len() != b.0.len() { return false }
let mut n = 0;
for (date, atomic_unit, block) in &a.0 {
if *date != b.0[n].0 { return false }
if *atomic_unit != b.0[n].1 { return false }
if *block != b.0[n].2 { return false }
n += 1;
}
true
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
// Expected input: "NOTICE 2022-01-27 01:30:23.1377 P2Pool You received a payout of 0.000000000001 XMR in block 2642816"
pub fn parse_raw_payout_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.payout_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 {
error!("P2Pool | AtomicUnit parse error: [{}]", line);
AtomicUnit::new()
}
} else {
error!("P2Pool | AtomicUnit parse error: [{}]", line);
AtomicUnit::new()
};
// Block
let block = if let Some(word) = regex.block.find(line) {
if let Some(word) = regex.block_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 {
error!("P2Pool | Block parse error: [{}]", line);
HumanNumber::unknown()
}
} else {
error!("P2Pool | Block parse error: [{}]", line);
HumanNumber::unknown()
};
(date, atomic_unit, block)
}
// Expected input: "2022-01-27 01:30:23.1377 | 0.000000000001 XMR | Block 2,642,816"
pub fn parse_formatted_payout_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_float.find(line) {
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 {
error!("P2Pool | AtomicUnit parse error: [{}]", line);
AtomicUnit::new()
};
// Block
let block = match regex.block_comma.find(line) {
Some(b) => HumanNumber::from_str(b.as_str()),
None => { error!("P2Pool | Block parse error: [{}]", line); HumanNumber::unknown() },
};
(date, atomic_unit, block)
}
// Takes in input of ONLY P2Pool payout logs and converts it into a usable [PayoutOrd]
// It expects formatted log lines like this: "2022-04-11 00:20:17.2571 | 0.001371623621 XMR | Block 2,562,511"
// 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) + (SPACES, PIPES, MISC WORDS) * amount_of_lines
// The first three are more or less constants (monero block 10m is in 10,379 years...): [23, 14, 7] (sum: 44)
// Spaces, pipes, commas and words (XMR, Block): [19]
// Add 7 more bytes for wrapper type overhead and it's an even [70] 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(70 * amount_of_lines);
for line in log.lines() {
debug!("PayoutOrd | Parsing line: [{}]", line);
vec.push(Self::parse_formatted_payout_line(line, &regex));
}
*self = Self(vec);
}
// Takes the wrapper types, and pushes to existing [Self]
pub fn push(&mut self, date: String, atomic_unit: AtomicUnit, block: HumanNumber) {
self.0.push((date, atomic_unit, block));
}
// Takes the raw components (no wrapper types), convert them and pushes to existing [Self]
pub fn push_raw(&mut self, date: &str, atomic_unit: u64, 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: u64 = 0;
for (_, atomic_unit, _) in &self.0 {
sum += atomic_unit.to_u64();
}
AtomicUnit::from_u64(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 [u64] 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));
}
// These sorting functions take around [0.0035~] seconds on a Ryzen 5950x
// given a Vec filled with 1_000_000 elements, not bad.
pub fn sort_payout_low_to_high(&mut self) {
self.0.sort_by(|a, b| a.1.0.cmp(&b.1.0));
}
// Returns a reversed [Iter] of the [PayoutOrd]
// This is obviously faster than actually reordering the Vec.
pub fn rev_iter(&self) -> std::iter::Rev<std::slice::Iter<'_, (String, AtomicUnit, HumanNumber)>> {
self.0.iter().rev()
}
// 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#"2021-12-21 01:01:01.1111 | 0.001000000000 XMR | Block 1,234,567
2021-12-21 02:01:01.1111 | 0.002000000000 XMR | Block 2,345,678
2021-12-21 03:01:01.1111 | 0.003000000000 XMR | Block 3,456,789
"#;
let mut payout_ord = PayoutOrd::new();
println!("BEFORE: {}", payout_ord);
PayoutOrd::update_from_payout_log(&mut payout_ord, log);
println!("AFTER: {}", payout_ord);
assert_eq!(payout_ord.to_string(), log);
}
#[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_raw("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_u64(1), HumanNumber::from_u64(2654321)),
("2022-09-09 16:18:26.7582".to_string(), AtomicUnit::from_u64(1), HumanNumber::from_u64(2654322)),
("2022-09-10 11:15:21.1272".to_string(), AtomicUnit::from_u64(1), HumanNumber::from_u64(2654323)),
]);
println!("OG: {:#?}", payout_ord);
let sum = PayoutOrd::atomic_unit_sum(&payout_ord);
println!("SUM: {}", sum.to_u64());
assert_eq!(sum.to_u64(), 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_u64(1000000000), HumanNumber::from_u64(2654321)),
("2022-09-09 16:18:26.7582".to_string(), AtomicUnit::from_u64(2000000000), HumanNumber::from_u64(2654322)),
("2022-09-10 11:15:21.1272".to_string(), AtomicUnit::from_u64(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 payout_ord_is_same() {
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_u64(1000000000), HumanNumber::from_u64(2654321)),
("2022-09-09 16:18:26.7582".to_string(), AtomicUnit::from_u64(2000000000), HumanNumber::from_u64(2654322)),
("2022-09-10 11:15:21.1272".to_string(), AtomicUnit::from_u64(3000000000), HumanNumber::from_u64(2654323)),
]);
let payout_ord_2 = payout_ord.clone();
println!("1: {:#?}", payout_ord);
println!("2: {:#?}", payout_ord);
assert!(PayoutOrd::is_same(&payout_ord, &payout_ord_2) == true);
payout_ord.push_raw("2022-09-08 18:42:55.4636", 1000000000, 2654321);
println!("1: {:#?}", payout_ord);
println!("2: {:#?}", payout_ord);
assert!(PayoutOrd::is_same(&payout_ord, &payout_ord_2) == false);
}
#[test]
fn view_reverse_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_u64(1000000000), HumanNumber::from_u64(2654321)),
("2022-09-09 16:18:26.7582".to_string(), AtomicUnit::from_u64(2000000000), HumanNumber::from_u64(2654322)),
("2022-09-10 11:15:21.1272".to_string(), AtomicUnit::from_u64(3000000000), HumanNumber::from_u64(2654323)),
]);
println!("OG: {:#?}", payout_ord);
for (_, atomic_unit, _) in payout_ord.rev_iter() {
if atomic_unit.to_u64() == 3000000000 {
break
} else {
println!("expected: 3000000000, found: {}", atomic_unit);
panic!("not reversed");
}
}
}
}