diff --git a/Cargo.toml b/Cargo.toml index c0a9fbe..1c359a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,7 +64,7 @@ rand = "0.8.5" regex = { version = "1.10.3", default-features = false, features = ["perf"] } rfd = "0.14.0" serde = { version = "1.0.197", features = ["rc", "derive"] } -serde_json = "1.0" +serde_json = "1.0.114" sysinfo = { version = "0.30.5", default-features = false } tls-api = "0.9.0" tokio = { version = "1.36.0", features = ["rt", "time", "macros", "process"] } diff --git a/NOTES_ALGORITHM.md b/NOTES_ALGORITHM.md index 90d3a7c..1f0a071 100644 --- a/NOTES_ALGORITHM.md +++ b/NOTES_ALGORITHM.md @@ -71,6 +71,27 @@ The mHR is calculated depending on the sidechain the p2pool is mining on. The XvB process will check every ten minutes the last 15 minutes average HR and decide when to switch (in seconds) for the ten next minutes. (first p2pool then XvB). *Need to see the time for Xmrig takes to set the new settings by API.* When the time to switch arrives, XvB process will send a request to Xmrig to change the node used. +### Modification of config of xmrig + +The following 4 attributes must be applied to xmrig config when mining to XvB node. + +```ignore + "url": "xvb node:4247" + "user": "user id", + "keepalive": true, + "tls": true, +``` +Or to return back to p2pool + +```ignore + "url": "127.0.0.1:3333" + "user": "Gupax_v1_3_5", + "keepalive": false, + "tls": false, +``` + +The HTTP API of xmrig requires to give a full config. +The current config will be requested, modified and sent back. [^1]: https://p2pool.io/mini/api/pool/stats [^2]: https://github.com/SChernykh/p2pool?tab=readme-ov-file#how-payouts-work-in-p2pool diff --git a/src/app/panels/bottom.rs b/src/app/panels/bottom.rs index 810085f..a1470ef 100644 --- a/src/app/panels/bottom.rs +++ b/src/app/panels/bottom.rs @@ -535,7 +535,12 @@ impl crate::app::App { .on_hover_text("Restart Xvb") .clicked() { - Helper::restart_xvb(&self.helper, &self.state.xvb, &self.state.p2pool); + Helper::restart_xvb( + &self.helper, + &self.state.xvb, + &self.state.p2pool, + &self.state.xmrig, + ); } if key.is_down() && !wants_input || ui @@ -569,7 +574,12 @@ impl crate::app::App { .on_disabled_hover_text(XVB_NOT_CONFIGURED) .clicked() { - Helper::start_xvb(&self.helper, &self.state.xvb, &self.state.p2pool); + Helper::start_xvb( + &self.helper, + &self.state.xvb, + &self.state.p2pool, + &self.state.xmrig, + ); } } }); diff --git a/src/app/panels/middle/mod.rs b/src/app/panels/middle/mod.rs index ac66d73..ca401c5 100644 --- a/src/app/panels/middle/mod.rs +++ b/src/app/panels/middle/mod.rs @@ -161,7 +161,7 @@ path_xmr: {:#?}\n } Tab::Xvb => { debug!("App | Entering [XvB] Tab"); - crate::disk::state::Xvb::show(&mut self.state.xvb, self.size, &self.state.p2pool.address, ctx, ui, &self.xvb_api, lock!(self.xvb).is_alive()); + crate::disk::state::Xvb::show(&mut self.state.xvb, self.size, &self.state.p2pool.address, ctx, ui, &self.xvb_api, lock!(self.xvb).is_alive()&& !lock!(self.xvb).is_syncing() && !lock!(self.xvb).is_not_mining()); } } }); diff --git a/src/app/panels/middle/xvb.rs b/src/app/panels/middle/xvb.rs index 697eb8a..05ff64a 100644 --- a/src/app/panels/middle/xvb.rs +++ b/src/app/panels/middle/xvb.rs @@ -3,6 +3,7 @@ use std::sync::{Arc, Mutex}; use egui::TextStyle::{self, Name}; use egui::{vec2, Hyperlink, Image, RichText, TextEdit, Ui, Vec2}; use log::debug; +use readable::byte::Byte; use crate::helper::xvb::PubXvbApi; use crate::utils::constants::{ @@ -26,7 +27,7 @@ impl crate::disk::state::Xvb { _ctx: &egui::Context, ui: &mut egui::Ui, api: &Arc>, - xvb_is_alive: bool, + private_stats: bool, ) { let website_height = size.y / 10.0; let width = size.x; @@ -104,75 +105,90 @@ impl crate::disk::state::Xvb { } // private stats let priv_stats = &lock!(api).stats_priv; - ui.set_enabled(xvb_is_alive); // ui.vertical_centered(|ui| { - ui.add_space(SPACE * 2.0); - ui.horizontal(|ui| { - // widget takes a third less space for two separator. - let width_stat = (ui.available_width() / 5.0) - - ((24.0 + ui.style().spacing.item_spacing.x + SPACE) / 5.0); - // 0.0 means minimum - let height_stat = 0.0; - let size_stat = vec2(width_stat, height_stat); - let round = match &priv_stats.round_participate { - Some(r) => r.to_string(), - None => "None".to_string(), - }; - ui.add_sized(size_stat, |ui: &mut Ui| { - ui.group(|ui| { - let size_stat = vec2( - ui.available_width(), - 0.0, // + ui.spacing().item_spacing.y, - ); - ui.add_sized(size_stat, |ui: &mut Ui| { - ui.vertical_centered(|ui| { - ui.label(XVB_FAILURE_FIELD); - ui.label(priv_stats.fails.to_string()); - }) - .response - }); - ui.separator(); - ui.add_sized(size_stat, |ui: &mut Ui| { - ui.vertical_centered(|ui| { - ui.label(XVB_DONATED_1H_FIELD); - ui.label(priv_stats.donor_1hr_avg.to_string()); - }) - .response - }); - ui.separator(); - ui.add_sized(size_stat, |ui: &mut Ui| { - ui.vertical_centered(|ui| { - ui.label(XVB_DONATED_24H_FIELD); - ui.label(priv_stats.donor_24hr_avg.to_string()); - }) - .response - }); - ui.separator(); - ui.add_enabled_ui(priv_stats.round_participate.is_some(), |ui| { + ui.add_enabled_ui(private_stats, |ui| { + ui.add_space(SPACE * 2.0); + ui.horizontal(|ui| { + // widget takes a third less space for two separator. + let width_stat = (ui.available_width() / 5.0) + - ((24.0 + ui.style().spacing.item_spacing.x + SPACE) / 5.0); + // 0.0 means minimum + let height_stat = 0.0; + let size_stat = vec2(width_stat, height_stat); + let round = match &priv_stats.round_participate { + Some(r) => r.to_string(), + None => "None".to_string(), + }; + ui.add_sized(size_stat, |ui: &mut Ui| { + ui.group(|ui| { + let size_stat = vec2( + ui.available_width(), + 0.0, // + ui.spacing().item_spacing.y, + ); ui.add_sized(size_stat, |ui: &mut Ui| { ui.vertical_centered(|ui| { - ui.label(XVB_ROUND_TYPE_FIELD); - ui.label(round); + ui.label(XVB_FAILURE_FIELD); + ui.label(priv_stats.fails.to_string()); }) .response - }) - .on_disabled_hover_text("You do not yet have a share in the PPLNS Window."); - }); - ui.separator(); - ui.add_sized(size_stat, |ui: &mut Ui| { - ui.vertical_centered(|ui| { - ui.label(XVB_WINNER_FIELD); - ui.label( - priv_stats - .win_current - .then(|| "You are Winning the round !") - .unwrap_or("You are not the winner"), + }); + ui.separator(); + ui.add_sized(size_stat, |ui: &mut Ui| { + ui.vertical_centered(|ui| { + ui.label(XVB_DONATED_1H_FIELD); + ui.label( + [ + Byte::from(priv_stats.donor_1hr_avg).to_string(), + "H/s".to_string(), + ] + .concat(), + ); + }) + .response + }); + ui.separator(); + ui.add_sized(size_stat, |ui: &mut Ui| { + ui.vertical_centered(|ui| { + ui.label(XVB_DONATED_24H_FIELD); + ui.label( + [ + Byte::from(priv_stats.donor_24hr_avg).to_string(), + "H/s".to_string(), + ] + .concat(), + ); + }) + .response + }); + ui.separator(); + ui.add_enabled_ui(priv_stats.round_participate.is_some(), |ui| { + ui.add_sized(size_stat, |ui: &mut Ui| { + ui.vertical_centered(|ui| { + ui.label(XVB_ROUND_TYPE_FIELD); + ui.label(round); + }) + .response + }) + .on_disabled_hover_text( + "You do not yet have a share in the PPLNS Window.", ); - }) - .response - }); - }) - .response + }); + ui.separator(); + ui.add_sized(size_stat, |ui: &mut Ui| { + ui.vertical_centered(|ui| { + ui.label(XVB_WINNER_FIELD); + ui.label( + priv_stats + .win_current + .then(|| "You are Winning the round !") + .unwrap_or("You are not the winner"), + ); + }) + .response + }); + }) + .response + }); }); }); // Rules link help diff --git a/src/disk/state.rs b/src/disk/state.rs index 42faac6..8a50fc2 100644 --- a/src/disk/state.rs +++ b/src/disk/state.rs @@ -255,6 +255,37 @@ pub enum XvbNode { NorthAmerica, #[default] Europe, + P2pool, +} +impl XvbNode { + pub fn url(&self) -> String { + match self { + Self::NorthAmerica => String::from(XVB_NODE_NA), + Self::Europe => String::from(XVB_NODE_EU), + Self::P2pool => String::from("127.0.0.1:3333"), + } + } + pub fn user(&self, address: &str) -> String { + match self { + Self::NorthAmerica => address.chars().take(8).collect(), + Self::Europe => address.chars().take(8).collect(), + Self::P2pool => GUPAX_VERSION_UNDERSCORE.to_string(), + } + } + pub fn tls(&self) -> bool { + match self { + Self::NorthAmerica => true, + Self::Europe => true, + Self::P2pool => false, + } + } + pub fn keepalive(&self) -> bool { + match self { + Self::NorthAmerica => true, + Self::Europe => true, + Self::P2pool => false, + } + } } #[derive(Clone, Debug, Deserialize, Serialize)] diff --git a/src/helper/xmrig.rs b/src/helper/xmrig.rs index cb1da5b..bd5d555 100644 --- a/src/helper/xmrig.rs +++ b/src/helper/xmrig.rs @@ -1,12 +1,15 @@ +use crate::disk::state::XvbNode; use crate::helper::{ProcessName, ProcessSignal, ProcessState}; use crate::regex::XMRIG_REGEX; use crate::utils::human::HumanNumber; use crate::utils::sudo::SudoState; use crate::{constants::*, macros::*}; +use anyhow::{anyhow, Result}; use log::*; use readable::num::Unsigned; use readable::up::Uptime; use serde::{Deserialize, Serialize}; +use serde_json::Value; use std::path::Path; use std::{ fmt::Write, @@ -199,8 +202,6 @@ impl Helper { args.push("127.0.0.1".to_string()); // HTTP API IP args.push("--http-port".to_string()); args.push("18088".to_string()); // HTTP API Port - args.push(format!("--http-access-token={}", state.token)); // HTTP API Port - args.push("--http-no-restricted".to_string()); if state.pause != 0 { args.push("--pause-on-active".to_string()); args.push(state.pause.to_string()); @@ -290,6 +291,8 @@ impl Helper { }; } } + args.push(format!("--http-access-token={}", state.token)); // HTTP API Port + args.push("--http-no-restricted".to_string()); (args, format!("{}:{}", api_ip, api_port)) } @@ -622,8 +625,8 @@ pub struct PubXmrigApi { pub diff: String, pub accepted: String, pub rejected: String, - pub hashrate_raw: f32, + pub hashrate_raw_15m: f32, } impl Default for PubXmrigApi { @@ -644,6 +647,7 @@ impl PubXmrigApi { accepted: UNKNOWN_DATA.to_string(), rejected: UNKNOWN_DATA.to_string(), hashrate_raw: 0.0, + hashrate_raw_15m: 0.0, } } @@ -701,6 +705,10 @@ impl PubXmrigApi { Some(Some(h)) => *h, _ => 0.0, }; + let hashrate_raw_15m = match private.hashrate.total.last() { + Some(Some(h)) => *h, + _ => 0.0, + }; *public = Self { worker_id: private.worker_id, @@ -710,6 +718,7 @@ impl PubXmrigApi { accepted: Unsigned::from(private.connection.accepted as usize).to_string(), rejected: Unsigned::from(private.connection.rejected as usize).to_string(), hashrate_raw, + hashrate_raw_15m, ..std::mem::take(&mut *public) } } @@ -758,6 +767,61 @@ impl PrivXmrigApi { let body = hyper::body::to_bytes(response?.body_mut()).await?; Ok(serde_json::from_slice::(&body)?) } + // #[inline] + // // Replace config with new node + pub async fn update_xmrig_config( + client: &hyper::Client, + api_uri: &str, + token: &str, + node: XvbNode, + address: &str, + ) -> Result<()> { + // get config + let request = hyper::Request::builder() + .method("GET") + .header("Authorization", ["Bearer ", token].concat()) + .uri(api_uri) + .body(hyper::Body::empty())?; + let response = tokio::time::timeout( + std::time::Duration::from_millis(500), + client.request(request), + ) + .await?; + let body = hyper::body::to_bytes(response?.body_mut()).await?; + // deserialize to json + let mut config = serde_json::from_slice::(&body)?; + // modify node configuration + *config + .pointer_mut("/pools/0/url") + .ok_or_else(|| anyhow!("pools/0/url does not exist in xmrig config"))? = + node.url().into(); + *config + .pointer_mut("/pools/0/user") + .ok_or_else(|| anyhow!("pools/0/user does not exist in xmrig config"))? = + node.user(&address).into(); + *config + .pointer_mut("/pools/0/tls") + .ok_or_else(|| anyhow!("pools/0/tls does not exist in xmrig config"))? = + node.tls().into(); + *config + .pointer_mut("/pools/0/keepalive") + .ok_or_else(|| anyhow!("pools/0/keepalive does not exist in xmrig config"))? = + node.keepalive().into(); + // reconstruct body from new config + let body = hyper::body::Body::from(config.to_string()); + // send new config + let request = hyper::Request::builder() + .method("PUT") + .header("Authorization", ["Bearer ", token].concat()) + .uri(api_uri) + .body(body)?; + tokio::time::timeout( + std::time::Duration::from_millis(500), + client.request(request), + ) + .await??; + anyhow::Ok(()) + } } #[derive(Debug, Serialize, Deserialize, Clone, Copy)] diff --git a/src/helper/xvb.rs b/src/helper/xvb.rs index bcd15b6..bb12ee8 100644 --- a/src/helper/xvb.rs +++ b/src/helper/xvb.rs @@ -2,19 +2,28 @@ use anyhow::{bail, Result}; use bytes::Bytes; use derive_more::Display; use hyper::client::HttpConnector; -use hyper::StatusCode; +use hyper::{Client, StatusCode}; use hyper_tls::HttpsConnector; use log::{debug, error, info, warn}; use readable::up::Uptime; use serde::Deserialize; use std::fmt::Write; +use std::time::Duration; use std::{ sync::{Arc, Mutex}, thread, time::Instant, }; +use tokio::spawn; +use tokio::time::sleep_until; -use crate::utils::constants::{XVB_PUBLIC_ONLY, XVB_URL}; +use crate::disk::state::XvbNode; +use crate::helper::xmrig::PrivXmrigApi; +use crate::utils::constants::{ + BLOCK_PPLNS_WINDOW_MAIN, BLOCK_PPLNS_WINDOW_MINI, SECOND_PER_BLOCK_P2POOL, XMRIG_CONFIG_URI, + XVB_BUFFER, XVB_PUBLIC_ONLY, XVB_ROUND_DONOR_MEGA_MIN_HR, XVB_ROUND_DONOR_MIN_HR, + XVB_ROUND_DONOR_VIP_MIN_HR, XVB_ROUND_DONOR_WHALE_MIN_HR, XVB_TIME_ALGO, XVB_URL, +}; use crate::{ helper::{ProcessSignal, ProcessState}, utils::{ @@ -24,6 +33,7 @@ use crate::{ }; use super::p2pool::PubP2poolApi; +use super::xmrig::PubXmrigApi; use super::{Helper, Process}; impl Helper { @@ -37,6 +47,7 @@ impl Helper { helper: &Arc>, state_xvb: &crate::disk::state::Xvb, state_p2pool: &crate::disk::state::P2pool, + state_xmrig: &crate::disk::state::Xmrig, ) { info!("XvB | Attempting to restart..."); lock2!(helper, xvb).signal = ProcessSignal::Restart; @@ -44,6 +55,7 @@ impl Helper { let helper = helper.clone(); let state_xvb = state_xvb.clone(); let state_p2pool = state_p2pool.clone(); + let state_xmrig = state_xmrig.clone(); // This thread lives to wait, start xmrig then die. thread::spawn(move || { while lock2!(helper, xvb).state != ProcessState::Waiting { @@ -52,7 +64,7 @@ impl Helper { } // Ok, process is not alive, start the new one! info!("XvB | Old process seems dead, starting new one!"); - Self::start_xvb(&helper, &state_xvb, &state_p2pool); + Self::start_xvb(&helper, &state_xvb, &state_p2pool, &state_xmrig); }); info!("XMRig | Restart ... OK"); } @@ -60,6 +72,7 @@ impl Helper { helper: &Arc>, state_xvb: &crate::disk::state::Xvb, state_p2pool: &crate::disk::state::P2pool, + state_xmrig: &crate::disk::state::Xmrig, ) { info!("XvB | setting state to Middle"); lock2!(helper, xvb).state = ProcessState::Middle; @@ -70,6 +83,8 @@ impl Helper { // needed to see if it is alive. For XvB process to function completely, p2pool node must be alive to check the shares in the pplns window. let process_p2pool = Arc::clone(&lock!(helper).p2pool); let gui_api_p2pool = Arc::clone(&lock!(helper).gui_api_p2pool); + let process_xmrig = Arc::clone(&lock!(helper).xmrig); + let gui_api_xmrig = Arc::clone(&lock!(helper).gui_api_xmrig); info!("XvB | cloning of state"); // Reset before printing to output. // Need to reset because values of stats would stay otherwise which could bring confusion even if panel is with a disabled theme. @@ -87,6 +102,7 @@ impl Helper { // verify if token and address are existent on XvB server let state_xvb = state_xvb.clone(); let state_p2pool = state_p2pool.clone(); + let state_xmrig = state_xmrig.clone(); info!("XvB | spawn watchdog"); thread::spawn(move || { @@ -96,8 +112,11 @@ impl Helper { process, &state_xvb, &state_p2pool, + &state_xmrig, gui_api_p2pool, process_p2pool, + gui_api_xmrig, + process_xmrig, ); }); } @@ -108,33 +127,29 @@ impl Helper { process: Arc>, state_xvb: &crate::disk::state::Xvb, state_p2pool: &crate::disk::state::P2pool, + state_xmrig: &crate::disk::state::Xmrig, gui_api_p2pool: Arc>, process_p2pool: Arc>, + gui_api_xmrig: Arc>, + process_xmrig: Arc>, ) { let https = HttpsConnector::new(); let client = hyper::Client::builder().build(https); - let resp = - XvbPrivStats::request_api(&client, &state_p2pool.address, &state_xvb.token).await; info!("XvB | verify address and token"); - match resp { - Ok(_) => { - let mut lock = lock!(process); - lock.state = ProcessState::Alive; - } - Err(err) => { - // send to console: token non existent for address on XvB server - warn!("Xvb | Start ... Partially failed because token and associated address are not existent on XvB server: {}\n", err); - // output the error to console - if let Err(e) = writeln!( + if let Err(err) = + XvbPrivStats::request_api(&client, &state_p2pool.address, &state_xvb.token).await + { + // send to console: token non existent for address on XvB server + warn!("Xvb | Start ... Partially failed because token and associated address are not existent on XvB server: {}\n", err); + // output the error to console + if let Err(e) = writeln!( lock!(gui_api).output, - "Failure to retrieve private stats from XvB server.\nError: {}\n{}\n", + "Token and associated address are not valid on XvB API.\nCheck if you are registered.\nError: {}\n", err, - XVB_PUBLIC_ONLY ) { error!("XvB Watchdog | GUI status write failed: {}", e); } - lock!(process).state = ProcessState::NotMining; - } + lock!(process).state = ProcessState::NotMining; } info!("XvB | verify p2pool node"); if !lock!(process_p2pool).is_alive() { @@ -143,7 +158,21 @@ impl Helper { // output the error to console if let Err(e) = writeln!( lock!(gui_api).output, - "Failure to completely start XvB process because p2pool instance is not running.\n", + "P2pool process is not running.\nCheck the P2pool Tab\n", + ) { + error!("XvB Watchdog | GUI status write failed: {}", e); + } + + lock!(process).state = ProcessState::Syncing; + } + + if !lock!(process_xmrig).is_alive() { + // send to console: p2pool process is not running + warn!("Xvb | Start ... Partially failed because Xmrig instance is not running."); + // output the error to console + if let Err(e) = writeln!( + lock!(gui_api).output, + "XMRig process is not running.\nCheck the Xmrig Tab.\n", ) { error!("XvB Watchdog | GUI status write failed: {}", e); } @@ -151,12 +180,17 @@ impl Helper { lock!(process).state = ProcessState::Syncing; } info!("XvB | print to console state"); - if lock!(process).state != ProcessState::Alive { - if let Err(e) = writeln!(lock!(gui_api).output, "{}\n", XVB_PUBLIC_ONLY,) { + if lock!(process).state != ProcessState::Middle { + if let Err(e) = writeln!( + lock!(gui_api).output, + "XvB partially started.\n{}\n", + XVB_PUBLIC_ONLY, + ) { error!("XvB Watchdog | GUI status write failed: {}", e); } } else { - info!("XvB started"); + info!("XvB Fully started"); + lock!(process).state = ProcessState::Alive; if let Err(e) = writeln!(lock!(gui_api).output, "XvB started\n") { error!("XvB Watchdog | GUI status write failed: {}", e); } @@ -177,19 +211,23 @@ impl Helper { // let mut old_shares = 0; let mut time_last_share: Option = None; let start = lock!(process).start; + let mut start_algorithm = tokio::time::Instant::now(); info!("XvB | Entering watchdog mode... woof!"); loop { debug!("XvB Watchdog | ----------- Start of loop -----------"); - // verify if p2pool node is running with correct token. + // if address and token valid, verify if p2pool and xmrig are running, else XvBmust be reloaded with another token/address to start verifying the other process. if lock!(process).state != ProcessState::NotMining { - if lock!(process_p2pool).is_alive() { + // verify if p2pool node and xmrig are running + if lock!(process_p2pool).is_alive() && lock!(process_xmrig).is_alive() { // verify if state is to changed if lock!(process).state == ProcessState::Syncing { - info!("XvB | started this time with p2pool"); + info!("XvB | started this time with p2pool and xmrig"); + *lock!(pub_api) = PubXvbApi::new(); + *lock!(gui_api) = PubXvbApi::new(); lock!(process).state = ProcessState::Alive; if let Err(e) = writeln!( lock!(gui_api).output, - "XvB is now started because p2pool node came online.\n", + "XvB is now started because p2pool and xmrig came online.\n", ) { error!("XvB Watchdog | GUI status write failed: {}", e); } @@ -198,14 +236,15 @@ impl Helper { // verify if the state is changing because p2pool is not alive anymore. if lock!(process).state != ProcessState::Syncing { info!("XvB | stop partially because p2pool is not alive anymore."); - lock!(process).state = ProcessState::Alive; + *lock!(pub_api) = PubXvbApi::new(); + *lock!(gui_api) = PubXvbApi::new(); + lock!(process).state = ProcessState::Syncing; if let Err(e) = writeln!( lock!(gui_api).output, - "XvB is now partially stopped because p2pool node came offline.\n", + "XvB is now partially stopped because p2pool node or xmrig came offline.\nCheck P2pool and Xmrig Tabs", ) { error!("XvB Watchdog | GUI status write failed: {}", e); } - lock!(process).state = ProcessState::Syncing } } } @@ -307,13 +346,27 @@ impl Helper { let round = if share { let stats_priv = &lock!(pub_api).stats_priv; match ( - stats_priv.donor_1hr_avg / 1000.0, - stats_priv.donor_24hr_avg / 1000.0, + stats_priv.donor_1hr_avg as u32, + stats_priv.donor_24hr_avg as u32, ) { - x if x.0 > 1000.0 && x.1 > 1000.0 => Some(XvbRound::DonorMega), - x if x.0 > 100.0 && x.1 > 100.0 => Some(XvbRound::DonorWhale), - x if x.0 > 10.0 && x.1 > 10.0 => Some(XvbRound::DonorVip), - x if x.0 > 1.0 && x.1 > 1.0 => Some(XvbRound::Donor), + x if x.0 > XVB_ROUND_DONOR_MEGA_MIN_HR + && x.1 > XVB_ROUND_DONOR_MEGA_MIN_HR => + { + Some(XvbRound::DonorMega) + } + x if x.0 > XVB_ROUND_DONOR_WHALE_MIN_HR + && x.1 > XVB_ROUND_DONOR_WHALE_MIN_HR => + { + Some(XvbRound::DonorWhale) + } + x if x.0 > XVB_ROUND_DONOR_VIP_MIN_HR + && x.1 > XVB_ROUND_DONOR_VIP_MIN_HR => + { + Some(XvbRound::DonorVip) + } + x if x.0 > XVB_ROUND_DONOR_MIN_HR && x.1 > XVB_ROUND_DONOR_MIN_HR => { + Some(XvbRound::Donor) + } (_, _) => Some(XvbRound::Vip), } } else { @@ -328,20 +381,83 @@ impl Helper { { lock!(pub_api).stats_priv.win_current = true } + // if 10 minutes passed since last check - if lock!(gui_api).tick_distribute_hr > (60 * 10) { + // the first 15 minutes, the HR of xmrig will be 0.0, so xmrig will always mine on p2pool for 15m. + if start_algorithm.elapsed() >= Duration::from_secs(XVB_TIME_ALGO.into()) { + info!("Xvb Process | Algorithm is started"); + // the time that takes the algorithm do decide the next ten minutes could means less p2pool mining. It is solved by the buffer. + start_algorithm = tokio::time::Instant::now(); // request XMrig to mine on P2pool + info!("Xvb Process | request to mine on p2pool"); + let client: hyper::Client = + hyper::Client::builder().build(hyper::client::HttpConnector::new()); + let api_uri = ["http://127.0.0.1:18088/", XMRIG_CONFIG_URI].concat(); + if let Err(err) = PrivXmrigApi::update_xmrig_config( + &client, + &api_uri, + &state_xmrig.token, + XvbNode::P2pool, + &state_p2pool.address, + ) + .await + { + // show to console error about updating xmrig config + if let Err(e) = writeln!( + lock!(gui_api).output, + "Failure to update xmrig config with HTTP API.\nError: {}", + err + ) { + error!("XvB Watchdog | GUI status write failed: {}", e); + } + } + + // if share is in PW, + if share { + info!("Xvb Process | Algorithm share is in current window"); + // calcul minimum HR + + let hr = lock!(gui_api_xmrig).hashrate_raw_15m; + let min_hr = Helper::minimum_hashrate_share( + lock!(gui_api_p2pool).p2pool_difficulty_u64, + state_p2pool.mini, + ); + info!("Xvb Process | hr {}, min_hr: {} ", hr, min_hr); + + // calculate how much time can be spared + let mut spared_time = Helper::time_that_could_be_spared(hr, min_hr); + if spared_time > 0 { + // if not hero option + if !state_xvb.hero { + // calculate how much time needed to be spared to be in most round type minimum HR + buffer + spared_time = Helper::minimum_time_for_highest_accessible_round( + spared_time, + hr, + ); + } + info!("Xvb Process | spared time {} ", spared_time); + // sleep 10m less spared time then request XMrig to mine on XvB + let was_instant = start_algorithm.clone(); + let node = state_xvb.node.clone(); + let token = state_xmrig.token.clone(); + let address = state_p2pool.address.clone(); + let gui_api = gui_api.clone(); + spawn(async move { + Helper::sleep_then_update_node_xmrig( + was_instant, + spared_time, + &client, + &api_uri, + &token, + node, + &address, + gui_api, + ) + .await; + }); + } + } } - // if share is in PW, - // check average HR of last 15 minutes from XMrig. - // if HR + buffer >= mHR, - // calculate how much time can be spared - // if not hero option - // calculate how much time needed to be spared to be in most round type minimum HR + buffer - // fi - // sleep 10m less spared time then request XMrig to mine on XvB - // fi - // fi // instant saved for next check // fi } @@ -364,6 +480,61 @@ impl Helper { } } } + fn minimum_hashrate_share(difficulty: u64, mini: bool) -> f32 { + let pws = if mini { + BLOCK_PPLNS_WINDOW_MINI + } else { + BLOCK_PPLNS_WINDOW_MAIN + }; + (difficulty / (pws * SECOND_PER_BLOCK_P2POOL)) as f32 * XVB_BUFFER + } + fn time_that_could_be_spared(hr: f32, min_hr: f32) -> u32 { + // percent of time minimum + let minimum_time_required_on_p2pool = XVB_TIME_ALGO as f32 / (hr / min_hr); + let spared_time = XVB_TIME_ALGO as f32 - minimum_time_required_on_p2pool; + // if less than 10 seconds, XMRig could hardly have the time to mine anything. + if spared_time >= 10f32 { + return spared_time as u32; + } + 0 + } + fn minimum_time_for_highest_accessible_round(st: u32, hr: f32) -> u32 { + let hr_for_xvb = ((st as f32 / XVB_TIME_ALGO as f32) * hr) as u32; + match hr_for_xvb { + x if x > XVB_ROUND_DONOR_MEGA_MIN_HR => x - XVB_ROUND_DONOR_MEGA_MIN_HR, + x if x > XVB_ROUND_DONOR_WHALE_MIN_HR => x - XVB_ROUND_DONOR_WHALE_MIN_HR, + x if x > XVB_ROUND_DONOR_VIP_MIN_HR => x - XVB_ROUND_DONOR_VIP_MIN_HR, + x if x > XVB_ROUND_DONOR_MIN_HR => x - XVB_ROUND_DONOR_MIN_HR, + _ => 0, + } + } + async fn sleep_then_update_node_xmrig( + was_instant: tokio::time::Instant, + spared_time: u32, + client: &Client, + api_uri: &str, + token_xmrig: &str, + node: XvbNode, + address: &str, + gui_api: Arc>, + ) { + info!("Xvb Process | for now mine on p2pol "); + sleep_until(was_instant + Duration::from_secs((XVB_TIME_ALGO - spared_time) as u64)).await; + if let Err(err) = + PrivXmrigApi::update_xmrig_config(client, api_uri, token_xmrig, node, address).await + { + // show to console error about updating xmrig config + if let Err(e) = writeln!( + lock!(gui_api).output, + "Failure to update xmrig config with HTTP API.\nError: {}", + err + ) { + error!("XvB Watchdog | GUI status write failed: {}", e); + } + } else { + info!("Xvb Process | mining on XvB pool"); + } + } } //---------------------------------------------------------------------------------------------------- Public XvB API use serde_this_or_that::as_u64; @@ -553,6 +724,15 @@ fn signal_interrupt( //---------------------------------------------------------------------------------------------------- TEST #[cfg(test)] mod test { + // use std::{sync::Arc, thread, time::Instant}; + + // use crate::{ + // app::App, + // disk::state::XvbNode, + // helper::{xmrig::PrivXmrigApi, Helper}, + // utils::constants::XMRIG_CONFIG_URI, + // }; + use std::thread; use super::XvbPubStats; @@ -571,4 +751,39 @@ mod test { async fn corr(client: Client>) -> XvbPubStats { XvbPubStats::request_api(&client).await.unwrap() } + // #[test] + // fn update_xmrig_config() { + // let client: hyper::Client = + // hyper::Client::builder().build(hyper::client::HttpConnector::new()); + // let node = XvbNode::Europe; + // let api_uri = ["http://127.0.0.1:18088/", XMRIG_CONFIG_URI].concat(); + // // start app + // let app = App::new(Instant::now()); + // // start xmrig + + // Helper::start_xmrig( + // &app.helper, + // &app.state.xmrig, + // &app.state.gupax.absolute_xmrig_path, + // Arc::clone(&app.sudo), + // ); + // let token = app.state.xmrig.token; + // let address = app.state.p2pool.address; + // // change config + // thread::spawn(move || req_update_config(client, &api_uri, &token, node, &address)) + // .join() + // .unwrap(); + // } + // #[tokio::main] + // async fn req_update_config( + // client: hyper::Client, + // api_uri: &str, + // token: &str, + // node: XvbNode, + // address: &str, + // ) { + // PrivXmrigApi::replace_xmrig_config(client, &api_uri, token, node, address) + // .await + // .unwrap() + // } } diff --git a/src/inits.rs b/src/inits.rs index ec71af9..dc2e815 100644 --- a/src/inits.rs +++ b/src/inits.rs @@ -208,7 +208,7 @@ pub fn init_auto(app: &mut App) { } // [Auto-XvB] if app.state.gupax.auto_xvb { - Helper::start_xvb(&app.helper, &app.state.xvb, &app.state.p2pool); + Helper::start_xvb(&app.helper, &app.state.xvb, &app.state.p2pool, &app.state.xmrig); } else { info!("Skipping auto-xvb..."); diff --git a/src/utils/constants.rs b/src/utils/constants.rs index 0e5dc72..36f2ee6 100644 --- a/src/utils/constants.rs +++ b/src/utils/constants.rs @@ -86,6 +86,7 @@ pub const P2POOL_API_PATH_NETWORK: &str = "network/stats"; #[cfg(target_family = "unix")] pub const P2POOL_API_PATH_POOL: &str = "pool/stats"; pub const XMRIG_API_URI: &str = "1/summary"; // The default relative URI of XMRig's API +pub const XMRIG_CONFIG_URI: &str = "1/config"; // The default relative URI of XMRig's API config // Process state tooltips (online, offline, etc) pub const P2POOL_ALIVE: &str = "P2Pool is online and fully synchronized"; @@ -410,9 +411,15 @@ pub const XMRIG_PATH_EMPTY: &str = "XMRig PATH is empty! To fix: goto the [G // XvB pub const XVB_HELP: &str = "You need to register an account by clicking on the link above to get your token with the same p2pool XMR address you use for payment."; pub const XVB_URL: &str = "https://xmrvsbeast.com"; -pub const XVB_URL_PUBLIC_API: &str = "https://xmrvsbeast.com/p2pool/stats"; -pub const XVB_URL_RULES: &str = "https://xmrvsbeast.com/p2pool/rules.html"; +pub const XVB_URL_PUBLIC_API: &str = "https://xmrvsbeast.com/p2pool/stats"; +pub const XVB_NODE_EU: &str = "eu.xmrvsbeast.com:4247"; +pub const XVB_NODE_NA: &str = "na.xmrvsbeast.com:4247"; +pub const XVB_URL_RULES: &str = "https://xmrvsbeast.com/p2pool/rules.html"; +// buffer in percentage of HR to have plus the requirement. +pub const XVB_BUFFER: f32 = 1.05; +// time in second the algorithm will distribute the HR +pub const XVB_TIME_ALGO: u32 = 600; pub const XVB_TOKEN_LEN: usize = 9; pub const XVB_HERO_SELECT: &str = "Donate all spared hashrate to the raffle, even if there is more than enough to be in the most highest round type possible"; @@ -422,6 +429,10 @@ pub const XVB_DONATED_1H_FIELD: &str = "Donated last hour"; pub const XVB_DONATED_24H_FIELD: &str = "Donated last 24 hours"; pub const XVB_ROUND_TYPE_FIELD: &str = "Round"; pub const XVB_WINNER_FIELD: &str = "Win"; +pub const XVB_ROUND_DONOR_MIN_HR: u32 = 1000; +pub const XVB_ROUND_DONOR_VIP_MIN_HR: u32 = 10000; +pub const XVB_ROUND_DONOR_WHALE_MIN_HR: u32 = 100000; +pub const XVB_ROUND_DONOR_MEGA_MIN_HR: u32 = 1000000; // CLI argument messages pub const ARG_HELP: &str = r#"USAGE: ./gupax [--flag] @@ -449,8 +460,13 @@ For more information, see link below: pub const UNKNOWN_DATA: &str = "???"; // Time PPLNS WINDOW in seconds // it is an estimation based on number of block in a pplns window and block time (10s). The difficulty of the network should adapt to get close to this value. -pub const TIME_PPLNS_WINDOW_MINI: Duration = Duration::from_secs(2160 * 10); -pub const TIME_PPLNS_WINDOW_MAIN: Duration = Duration::from_secs(363 * 10); +pub const BLOCK_PPLNS_WINDOW_MINI: u64 = 2160; +pub const BLOCK_PPLNS_WINDOW_MAIN: u64 = 363; +pub const SECOND_PER_BLOCK_P2POOL: u64 = 10; +pub const TIME_PPLNS_WINDOW_MINI: Duration = + Duration::from_secs(BLOCK_PPLNS_WINDOW_MINI * SECOND_PER_BLOCK_P2POOL); +pub const TIME_PPLNS_WINDOW_MAIN: Duration = + Duration::from_secs(BLOCK_PPLNS_WINDOW_MAIN * SECOND_PER_BLOCK_P2POOL); use std::time::Duration;