feat: manage selection and fallback nodes of XvB

feat: ping, select nodes of XvB process
feat: reload config of xmrig to fallback to backup node
fix: status of xmrig node used in real time
feat: src readme technical differences with upstream
This commit is contained in:
Louis-Marie Baer 2024-03-18 11:26:03 +01:00
parent e1c3fee92c
commit cfb6d0971c
12 changed files with 401 additions and 147 deletions

View file

@ -5,9 +5,6 @@
[XvB Bounty](https://bounties.monero.social/posts/105)
- [x] upgrade deps
- [ ] fix clippy
- [ ] better organize some new code
- [ ] merge commits from upstream
- [x] separate logic in smaller modules
- [x] new tab XvB
- [x] logo
@ -17,13 +14,17 @@
- [x] hero checkbox
- [x] log section
- [x] state of XvB process
- [ ] algorithm decisions
- [ ] selected XvB node
- [ ] algorithm decisions info
- [x] private stats
- [x] round type in
- [x] win or loose
- [ ] fix: remove B symbol for HR
- [ ] fix: symbol for HR
- [x] new process for XvB
- [x] update preferred XvB node based on ping and backup
- [x] fix: xmrig will not do anything if node is not responding. Need to parse output of xmrig for error and update nodes.
- [x] status process XvB
- [x] status process XMRig node in real time.
- [x] public information from [API](https://xmrvsbeast.com/p2pool/stats)
- [x] stop, start, restart buttons
- [x] button to autostart
@ -44,3 +45,10 @@
- [ ] adapt doc for new code
- [ ] cargo package metadata
- [ ] pgp signatures
- [ ] fix clippy
- [ ] better organize some new code
- [ ] merge commits from upstream
- [ ] tests for new functions
- [ ] pre-release
- [ ] feedback
- [ ] release

View file

@ -15,6 +15,13 @@
- [Registry Edit](#registry-edit)
- [Windows vs Unix](#windows-vs-unix)
## Technical differences with upstream Gupax
Status of process for Xmrig use for some information an image of data when the process started.
The node of xmrig in upstream can not change without a restart of the process.In this fork, the node used by xmrig needs to be updated without restart (using the config HTTP API of xmrig).
So Gupaxx need to refresh the value of status tab submenu process for xmrig where before the values could not change without a restart of the process.
The field node from ImgXmrig needs to be moved to PubXvbApi. This value must be updated by xmrig at start and by XvB process at runtime.
## Structure
| File/Folder | Purpose |
|--------------|---------|

View file

@ -605,7 +605,7 @@ fn status_p2pool(state: ProcessState, ui: &mut Ui, size: Vec2) {
color = ORANGE;
P2POOL_SYNCING
}
Middle | Waiting | NotMining => {
Middle | Waiting | NotMining | OfflineNodesAll => {
color = YELLOW;
P2POOL_MIDDLE
}
@ -628,7 +628,7 @@ fn status_xmrig(state: ProcessState, ui: &mut Ui, size: Vec2) {
color = RED;
XMRIG_FAILED
}
NotMining => {
NotMining | OfflineNodesAll => {
color = ORANGE;
XMRIG_NOT_MINING
}
@ -654,7 +654,7 @@ fn status_xvb(state: ProcessState, ui: &mut Ui, size: Vec2) {
color = RED;
XVB_FAILED
}
NotMining | Syncing => {
NotMining | Syncing | OfflineNodesAll => {
color = ORANGE;
XVB_PUBLIC_ONLY
}

View file

@ -167,14 +167,11 @@ fn p2pool(
.on_hover_text(STATUS_P2POOL_SHARES);
ui.add_sized(
[width, height],
Label::new(format!(
"{}",
if let Some(s) = api.shares_found {
Label::new((if let Some(s) = api.shares_found {
s.to_string()
} else {
UNKNOWN_DATA.to_string()
}
)),
}).to_string()),
);
ui.add_sized(
[width, height],
@ -317,7 +314,7 @@ fn xmrig(
),
)
.on_hover_text(STATUS_XMRIG_CPU);
ui.add_sized([width, height], Label::new(format!("{}", api.resources)));
ui.add_sized([width, height], Label::new(api.resources.to_string()));
ui.add_sized(
[width, height],
Label::new(
@ -327,13 +324,13 @@ fn xmrig(
),
)
.on_hover_text(STATUS_XMRIG_HASHRATE);
ui.add_sized([width, height], Label::new(format!("{}", api.hashrate)));
ui.add_sized([width, height], Label::new(api.hashrate.to_string()));
ui.add_sized(
[width, height],
Label::new(RichText::new("Difficulty").underline().color(BONE)),
)
.on_hover_text(STATUS_XMRIG_DIFFICULTY);
ui.add_sized([width, height], Label::new(format!("{}", api.diff)));
ui.add_sized([width, height], Label::new(api.diff.to_string()));
ui.add_sized(
[width, height],
Label::new(RichText::new("Shares").underline().color(BONE)),
@ -351,7 +348,7 @@ fn xmrig(
Label::new(RichText::new("Pool").underline().color(BONE)),
)
.on_hover_text(STATUS_XMRIG_POOL);
ui.add_sized([width, height], Label::new(&lock!(xmrig_img).url));
ui.add_sized([width, height], Label::new(api.node.to_string()));
ui.add_sized(
[width, height],
Label::new(RichText::new("Threads").underline().color(BONE)),

View file

@ -178,10 +178,8 @@ impl crate::disk::state::Xvb {
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"),
if priv_stats
.win_current { "You are Winning the round !" } else { "You are not the winner" },
);
})
.response

View file

@ -306,16 +306,16 @@ impl NodeData {
// This struct leaves out most fields on purpose,
// we only need a few to verify the node is ok.
#[derive(Debug, serde::Deserialize)]
struct GetInfo<'a> {
id: &'a str,
jsonrpc: &'a str,
result: GetInfoResult,
pub struct GetInfo<'a> {
pub id: &'a str,
pub jsonrpc: &'a str,
pub result: GetInfoResult,
}
#[derive(Debug, serde::Deserialize)]
struct GetInfoResult {
mainnet: bool,
synchronized: bool,
pub struct GetInfoResult {
pub mainnet: bool,
pub synchronized: bool,
}
//---------------------------------------------------------------------------------------------------- Ping data

View file

@ -247,45 +247,6 @@ pub struct Xmrig {
pub struct Xvb {
pub token: String,
pub hero: bool,
pub node: XvbNode,
}
#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize, Default)]
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)]

View file

@ -216,11 +216,15 @@ pub enum ProcessState {
Middle, // Process is in the middle of something ([re]starting/stopping), YELLOW!
Waiting, // Process was successfully killed by a restart, and is ready to be started again, YELLOW!
// Only for P2Pool, ORANGE.
// Only for P2Pool and XvB, ORANGE.
// XvB: Xmrig or P2pool are not alive
Syncing,
// Only for XMRig, ORANGE.
// Only for XMRig and XvB, ORANGE.
// XvB: token or address are invalid even if syntax correct
NotMining,
// XvB: if node of XvB become unusable (ex: offline).
OfflineNodesAll,
}
impl Default for ProcessState {
@ -235,6 +239,7 @@ pub enum ProcessSignal {
Start,
Stop,
Restart,
UpdateNodes,
}
impl Default for ProcessSignal {

View file

@ -1126,7 +1126,7 @@ impl PubP2poolApi {
if v > *old_shares {
// found new share
*old_shares = v;
return (true, Some(Instant::now()));
(true, Some(Instant::now()))
} else {
// no new share found this last minute, check if last share is still valid
if let Some(n) = last_time_found {
@ -1150,7 +1150,7 @@ impl PubP2poolApi {
}
} else {
// data from p2pool is not ready yet, so no share in pplns window.
return (false, None);
(false, None)
}
}
}

View file

@ -1,4 +1,3 @@
use crate::disk::state::XvbNode;
use crate::helper::{ProcessName, ProcessSignal, ProcessState};
use crate::regex::XMRIG_REGEX;
use crate::utils::human::HumanNumber;
@ -19,15 +18,18 @@ use std::{
thread,
time::*,
};
use tokio::spawn;
use super::xvb::XvbNode;
use super::{Helper, Process};
impl Helper {
#[cold]
#[inline(never)]
fn read_pty_xmrig(
async fn read_pty_xmrig(
output_parse: Arc<Mutex<String>>,
output_pub: Arc<Mutex<String>>,
reader: Box<dyn std::io::Read + Send>,
process_xvb: Arc<Mutex<Process>>,
) {
use std::io::BufRead;
let mut stdout = std::io::BufReader::new(reader).lines();
@ -50,6 +52,50 @@ impl Helper {
}
while let Some(Ok(line)) = stdout.next() {
// need to verify if node still working
// for that need to catch "connect error"
if line.contains("connect error") {
let process_xvb_c = process_xvb.clone();
// if waiting, it is restarting or already updating nodes, so do not send signal.
if lock!(process_xvb_c).state != ProcessState::Waiting {
info!("node is offline, switching to backup.");
lock!(process_xvb_c).signal = ProcessSignal::UpdateNodes;
}
// let address = state_p2pool.address.clone();
// let token = state_xmrig.token.clone();
// let pub_api_xvb_c = pub_api_xvb.clone();
// issue because while this future is executing, other connect error could arrive and repeat the process.
// spawn(async move {
// // need to create client
// let client_http = Arc::new(
// hyper::Client::builder().build(hyper::client::HttpConnector::new()),
// );
// // need to spawn and wait update fastest node.
// XvbNode::update_fastest_node(&client_http, &pub_api_xvb_c, &process_xvb_c)
// .await;
// // need to check new value of node.
// let node = lock!(pub_api_xvb_c).stats_priv.node.clone();
// // send new value to update config.
// if let Err(err) = PrivXmrigApi::update_xmrig_config(
// &client_http,
// XMRIG_CONFIG_URI,
// &token,
// &node,
// &address,
// )
// .await
// {
// // show to console error about updating xmrig config
// if let Err(e) = writeln!(
// lock!(pub_api_xvb_c).output,
// "Failure to update xmrig config with HTTP API.\nError: {}",
// err
// ) {
// error!("XvB Watchdog | GUI status write failed: {}", e);
// }
// }
// });
}
// println!("{}", line); // For debugging.
if let Err(e) = writeln!(lock!(output_parse), "{}", line) {
error!("XMRig PTY Parse | Output error: {}", e);
@ -134,7 +180,6 @@ impl Helper {
lock2!(helper, xmrig).state = ProcessState::Middle;
let (args, api_ip_port) = Self::build_xmrig_args_and_mutate_img(helper, state, path);
// Print arguments & user settings to console
crate::disk::print_dash(&format!("XMRig | Launch arguments: {:#?}", args));
info!("XMRig | Using path: [{}]", path.display());
@ -143,8 +188,10 @@ impl Helper {
let process = Arc::clone(&lock!(helper).xmrig);
let gui_api = Arc::clone(&lock!(helper).gui_api_xmrig);
let pub_api = Arc::clone(&lock!(helper).pub_api_xmrig);
let process_xvb = Arc::clone(&lock!(helper).xvb);
let path = path.to_path_buf();
let token = state.token.clone();
let img_xmrig = Arc::clone(&lock!(helper).img_xmrig);
thread::spawn(move || {
Self::spawn_xmrig_watchdog(
process,
@ -155,6 +202,8 @@ impl Helper {
sudo,
api_ip_port,
&token,
process_xvb,
&img_xmrig,
);
});
}
@ -210,6 +259,8 @@ impl Helper {
threads: state.current_threads.to_string(),
url: "127.0.0.1:3333 (Local P2Pool)".to_string(),
};
lock2!(helper, pub_api_xmrig).node = "127.0.0.1:3333 (Local P2Pool)".to_string();
api_ip = "127.0.0.1".to_string();
api_port = "18088".to_string();
@ -286,9 +337,10 @@ impl Helper {
args.push(state.pause.to_string());
} // Pause on active
*lock2!(helper, img_xmrig) = ImgXmrig {
url,
url: url.clone(),
threads: state.current_threads.to_string(),
};
lock2!(helper, pub_api_xmrig).node = url;
}
}
args.push(format!("--http-access-token={}", state.token)); // HTTP API Port
@ -329,6 +381,8 @@ impl Helper {
sudo: Arc<Mutex<SudoState>>,
mut api_ip_port: String,
token: &str,
process_xvb: Arc<Mutex<Process>>,
img_xmrig: &Arc<Mutex<ImgXmrig>>,
) {
// 1a. Create PTY
debug!("XMRig | Creating PTY...");
@ -383,8 +437,8 @@ impl Helper {
debug!("XMRig | Spawning PTY read thread...");
let output_parse = Arc::clone(&lock!(process).output_parse);
let output_pub = Arc::clone(&lock!(process).output_pub);
thread::spawn(move || {
Self::read_pty_xmrig(output_parse, output_pub, reader);
spawn(async move {
Self::read_pty_xmrig(output_parse, output_pub, reader, process_xvb).await;
});
let output_parse = Arc::clone(&lock!(process).output_parse);
let output_pub = Arc::clone(&lock!(process).output_pub);
@ -403,7 +457,8 @@ impl Helper {
// Reset stats before loop
*lock!(pub_api) = PubXmrigApi::new();
*lock!(gui_api) = PubXmrigApi::new();
// node used for process Status tab
lock!(gui_api).node = lock!(img_xmrig).url.clone();
// 5. Loop as watchdog
info!("XMRig | Entering watchdog mode... woof!");
loop {
@ -627,6 +682,7 @@ pub struct PubXmrigApi {
pub rejected: String,
pub hashrate_raw: f32,
pub hashrate_raw_15m: f32,
pub node: String,
}
impl Default for PubXmrigApi {
@ -648,15 +704,18 @@ impl PubXmrigApi {
rejected: UNKNOWN_DATA.to_string(),
hashrate_raw: 0.0,
hashrate_raw_15m: 0.0,
node: UNKNOWN_DATA.to_string(),
}
}
#[inline]
pub(super) fn combine_gui_pub_api(gui_api: &mut Self, pub_api: &mut Self) {
let output = std::mem::take(&mut gui_api.output);
let node = std::mem::take(&mut gui_api.node);
let buf = std::mem::take(&mut pub_api.output);
*gui_api = Self {
output,
node,
..std::mem::take(pub_api)
};
if !buf.is_empty() {
@ -767,14 +826,15 @@ impl PrivXmrigApi {
let body = hyper::body::to_bytes(response?.body_mut()).await?;
Ok(serde_json::from_slice::<Self>(&body)?)
}
// #[inline]
#[inline]
// // Replace config with new node
pub async fn update_xmrig_config(
client: &hyper::Client<hyper::client::HttpConnector>,
api_uri: &str,
token: &str,
node: XvbNode,
node: &XvbNode,
address: &str,
gui_api_xmrig: Arc<Mutex<PubXmrigApi>>,
) -> Result<()> {
// get config
let request = hyper::Request::builder()
@ -791,14 +851,15 @@ impl PrivXmrigApi {
// deserialize to json
let mut config = serde_json::from_slice::<Value>(&body)?;
// modify node configuration
let uri = [node.url(), ":".to_string(), node.port()].concat();
info!("replace xmrig config with node {}", uri);
*config
.pointer_mut("/pools/0/url")
.ok_or_else(|| anyhow!("pools/0/url does not exist in xmrig config"))? =
node.url().into();
.ok_or_else(|| anyhow!("pools/0/url does not exist in xmrig config"))? = uri.into();
*config
.pointer_mut("/pools/0/user")
.ok_or_else(|| anyhow!("pools/0/user does not exist in xmrig config"))? =
node.user(&address).into();
node.user(address).into();
*config
.pointer_mut("/pools/0/tls")
.ok_or_else(|| anyhow!("pools/0/tls does not exist in xmrig config"))? =
@ -813,6 +874,7 @@ impl PrivXmrigApi {
let request = hyper::Request::builder()
.method("PUT")
.header("Authorization", ["Bearer ", token].concat())
.header("Content-Type", "application/json")
.uri(api_uri)
.body(body)?;
tokio::time::timeout(
@ -820,6 +882,8 @@ impl PrivXmrigApi {
client.request(request),
)
.await??;
// update process status
lock!(gui_api_xmrig).node = node.to_string();
anyhow::Ok(())
}
}

View file

@ -2,7 +2,7 @@ use anyhow::{bail, Result};
use bytes::Bytes;
use derive_more::Display;
use hyper::client::HttpConnector;
use hyper::{Client, StatusCode};
use hyper::{Client, Request, StatusCode};
use hyper_tls::HttpsConnector;
use log::{debug, error, info, warn};
use readable::up::Uptime;
@ -17,11 +17,12 @@ use std::{
use tokio::spawn;
use tokio::time::sleep_until;
use crate::disk::state::XvbNode;
use crate::components::node::{GetInfo, TIMEOUT_NODE_PING};
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,
BLOCK_PPLNS_WINDOW_MAIN, BLOCK_PPLNS_WINDOW_MINI, GUPAX_VERSION_UNDERSCORE,
SECOND_PER_BLOCK_P2POOL, XMRIG_CONFIG_URI, XVB_BUFFER, XVB_NODE_EU, XVB_NODE_NA, XVB_NODE_PORT,
XVB_NODE_RPC, 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::{
@ -133,11 +134,15 @@ impl Helper {
gui_api_xmrig: Arc<Mutex<PubXmrigApi>>,
process_xmrig: Arc<Mutex<Process>>,
) {
// client for http and one for https, both will be valid for the thread scope.
let https = HttpsConnector::new();
let client = hyper::Client::builder().build(https);
let client_https = hyper::Client::builder().build(https);
// client http should be created only if process completely started, because it's not useful otherwise, but it would possibly be unitiliazed.
let client_http =
Arc::new(hyper::Client::builder().build(hyper::client::HttpConnector::new()));
info!("XvB | verify address and token");
if let Err(err) =
XvbPrivStats::request_api(&client, &state_p2pool.address, &state_xvb.token).await
XvbPrivStats::request_api(&client_https, &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);
@ -191,6 +196,13 @@ impl Helper {
} else {
info!("XvB Fully started");
lock!(process).state = ProcessState::Alive;
let pub_api_c = pub_api.clone();
let client_http_c = client_http.clone();
let process_c = process.clone();
// will check which pool to use, will send NotMining if
spawn(async move {
XvbNode::update_fastest_node(&client_http_c, &pub_api_c, &process_c).await
});
if let Err(e) = writeln!(lock!(gui_api).output, "XvB started\n") {
error!("XvB Watchdog | GUI status write failed: {}", e);
}
@ -235,7 +247,7 @@ impl Helper {
} else {
// 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.");
info!("XvB | stopped partially because p2pool is not alive anymore.");
*lock!(pub_api) = PubXvbApi::new();
*lock!(gui_api) = PubXvbApi::new();
lock!(process).state = ProcessState::Syncing;
@ -248,11 +260,20 @@ impl Helper {
}
}
}
// Set timer
let now = Instant::now();
// check signal
debug!("XvB | check signal");
if signal_interrupt(process.clone(), start, gui_api.clone()) {
if signal_interrupt(
process.clone(),
start,
&client_http,
&gui_api.clone(),
&gui_api_xmrig,
state_p2pool,
state_xmrig,
) {
break;
}
// verify if
@ -261,7 +282,7 @@ impl Helper {
let since = lock!(gui_api).tick;
if since >= 60 || since == 0 {
debug!("XvB Watchdog | Attempting HTTP public API request...");
match XvbPubStats::request_api(&client).await {
match XvbPubStats::request_api(&client_https).await {
Ok(new_data) => {
debug!("XvB Watchdog | HTTP API request OK");
lock!(&pub_api).stats_pub = new_data;
@ -288,7 +309,7 @@ impl Helper {
debug!("XvB Watchdog | Attempting HTTP private API request...");
// reload private stats
match XvbPrivStats::request_api(
&client,
&client_https,
&state_p2pool.address,
&state_xvb.token,
)
@ -386,31 +407,38 @@ impl Helper {
// 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.
// the time that takes the algorithm do decide the next ten minutes could means less p2pool mining. It is solved by the buffer and spawning requests.
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::HttpConnector> =
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);
let client_http_c = client_http.clone();
let gui_api_c = gui_api.clone();
let token_xmrig = Arc::new(state_xmrig.token.clone());
let token_xmrig_c = token_xmrig.clone();
let address = Arc::new(state_p2pool.address.clone());
let address_c = address.clone();
let gui_api_xmrig_c = gui_api_xmrig.clone();
spawn(async move {
if let Err(err) = PrivXmrigApi::update_xmrig_config(
&client_http_c,
XMRIG_CONFIG_URI,
&token_xmrig_c,
&XvbNode::P2pool,
&address_c,
gui_api_xmrig_c,
)
.await
{
// show to console error about updating xmrig config
if let Err(e) = writeln!(
lock!(gui_api_c).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 {
@ -437,23 +465,22 @@ impl Helper {
}
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();
let was_instant = start_algorithm;
let gui_api_c = gui_api.clone();
let client_http_c = client_http.clone();
let gui_api_xmrig_c = gui_api_xmrig.clone();
spawn(async move {
Helper::sleep_then_update_node_xmrig(
was_instant,
&was_instant,
spared_time,
&client,
&api_uri,
&token,
node,
&client_http_c,
XMRIG_CONFIG_URI,
&token_xmrig,
&address,
gui_api,
gui_api_c,
gui_api_xmrig_c,
)
.await;
.await
});
}
}
@ -509,19 +536,27 @@ impl Helper {
}
}
async fn sleep_then_update_node_xmrig(
was_instant: tokio::time::Instant,
was_instant: &tokio::time::Instant,
spared_time: u32,
client: &Client<HttpConnector>,
api_uri: &str,
token_xmrig: &str,
node: XvbNode,
address: &str,
gui_api: Arc<Mutex<PubXvbApi>>,
gui_api_xmrig: Arc<Mutex<PubXmrigApi>>,
) {
let node = lock!(gui_api).stats_priv.node.clone();
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
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,
gui_api_xmrig,
)
.await
{
// show to console error about updating xmrig config
if let Err(e) = writeln!(
@ -571,6 +606,19 @@ pub struct XvbPubStats {
pub reward_yearly: Vec<f64>,
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct XvbPrivStats {
pub fails: u8,
pub donor_1hr_avg: f32,
pub donor_24hr_avg: f32,
#[serde(skip)]
pub win_current: bool,
#[serde(skip)]
pub round_participate: Option<XvbRound>,
#[serde(skip)]
pub node: XvbNode,
}
impl XvbPubStats {
#[inline]
// Send an HTTP request to XvB's API, serialize it into [Self] and return it
@ -622,17 +670,6 @@ impl XvbPrivStats {
}
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct XvbPrivStats {
pub fails: u8,
pub donor_1hr_avg: f32,
pub donor_24hr_avg: f32,
#[serde(skip)]
pub win_current: bool,
#[serde(skip)]
pub round_participate: Option<XvbRound>,
}
#[derive(Debug, Clone, Default, Display, Deserialize)]
pub enum XvbRound {
#[default]
@ -652,6 +689,138 @@ pub enum XvbRound {
DonorMega,
}
#[derive(Clone, Debug, Default, PartialEq, Display)]
pub enum XvbNode {
#[display(fmt = "XvB North America Node")]
NorthAmerica,
#[default]
#[display(fmt = "XvB North European Node")]
Europe,
#[display(fmt = "Local P2pool")]
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"),
}
}
pub fn port(&self) -> String {
match self {
Self::NorthAmerica | Self::Europe => String::from(XVB_NODE_PORT),
Self::P2pool => String::from("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,
}
}
pub async fn update_fastest_node(
client: &Arc<Client<HttpConnector>>,
pub_api_xvb: &Arc<Mutex<PubXvbApi>>,
process_xvb: &Arc<Mutex<Process>>,
) {
let client_eu = client.clone();
let client_na = client.clone();
let ms_eu = spawn(async move { XvbNode::ping(&XvbNode::Europe.url(), &client_eu).await });
let ms_na =
spawn(async move { XvbNode::ping(&XvbNode::NorthAmerica.url(), &client_na).await });
let node = if let Ok(ms_eu) = ms_eu.await {
if let Ok(ms_na) = ms_na.await {
// if two nodes are up, compare ping latency and return fastest.
if ms_na != TIMEOUT_NODE_PING && ms_eu != TIMEOUT_NODE_PING {
if ms_na < ms_eu {
XvbNode::NorthAmerica
} else {
XvbNode::Europe
}
} else
// if only na is online, return it.
if ms_na != TIMEOUT_NODE_PING && ms_eu == TIMEOUT_NODE_PING {
XvbNode::NorthAmerica
} else
// if only eu is online, return it.
if ms_na == TIMEOUT_NODE_PING && ms_eu != TIMEOUT_NODE_PING {
XvbNode::Europe
} else {
// if P2pool is returned, it means none of the two nodes are available.
XvbNode::P2pool
}
} else {
error!("ping has failed !");
XvbNode::P2pool
}
} else {
error!("ping has failed !");
XvbNode::P2pool
};
if node == XvbNode::P2pool {
// if both nodes are dead, then the state of the process must be NodesOffline
info!("XvB node ping, all offline or ping failed, switching back to p2pool",);
lock!(process_xvb).state = ProcessState::OfflineNodesAll;
} else {
// if node is up and because update_fastest is used only if token/address is valid, it means XvB process is Alive.
info!("XvB node ping, both online and best is {}", node.url());
lock!(process_xvb).state = ProcessState::Alive;
}
lock!(pub_api_xvb).stats_priv.node = node;
}
async fn ping(ip: &str, client: &Client<HttpConnector>) -> u128 {
let request = Request::builder()
.method("POST")
.uri("http://".to_string() + ip + ":" + XVB_NODE_RPC + "/json_rpc")
.body(hyper::Body::from(
r#"{"jsonrpc":"2.0","id":"0","method":"get_info"}"#,
))
.expect("hyper request should build.");
let ms;
let now = Instant::now();
match tokio::time::timeout(Duration::from_secs(5), client.request(request)).await {
Ok(Ok(json_rpc)) => {
// Attempt to convert to JSON-RPC.
match hyper::body::to_bytes(json_rpc.into_body()).await {
Ok(b) => match serde_json::from_slice::<GetInfo<'_>>(&b) {
Ok(rpc) => {
if rpc.result.mainnet && rpc.result.synchronized {
ms = now.elapsed().as_millis();
} else {
ms = TIMEOUT_NODE_PING;
warn!("Ping | {ip} responded with valid get_info but is not in sync, remove this node!");
}
}
_ => {
ms = TIMEOUT_NODE_PING;
warn!("Ping | {ip} responded but with invalid get_info, remove this node!");
}
},
_ => ms = TIMEOUT_NODE_PING,
};
}
_ => ms = TIMEOUT_NODE_PING,
};
ms
}
}
impl PubXvbApi {
pub fn new() -> Self {
Self::default()
@ -680,7 +849,11 @@ impl PubXvbApi {
fn signal_interrupt(
process: Arc<Mutex<Process>>,
start: Instant,
gui_api: Arc<Mutex<PubXvbApi>>,
client_http: &Arc<Client<HttpConnector>>,
gui_api: &Arc<Mutex<PubXvbApi>>,
gui_api_xmrig: &Arc<Mutex<PubXmrigApi>>,
state_p2pool: &crate::disk::state::P2pool,
state_xmrig: &crate::disk::state::Xmrig,
) -> bool {
// Check SIGNAL
// check if STOP or RESTART Signal is given.
@ -718,6 +891,46 @@ fn signal_interrupt(
debug!("XvB Watchdog | Restart SIGNAL done, breaking");
lock!(process).state = ProcessState::Waiting;
return true;
// Check UPDATE NODES
} else if lock!(process).signal == ProcessSignal::UpdateNodes
&& lock!(process).state != ProcessState::Waiting
{
info!("XvB Watchdog | Signal has been given to ping and reselect Nodes.");
// if signal is waiting, he is restarting or already updating nodes.
// A signal has been given to ping the nodes and select the fastest.
let gui_api_c = gui_api.clone();
let gui_api_xmrig_c = gui_api_xmrig.clone();
let client_http_c = client_http.clone();
let process_c = process.clone();
let token_xmrig = state_xmrig.token.clone();
let address = state_p2pool.address.clone();
lock!(process).state = ProcessState::Waiting;
let node = lock!(gui_api).stats_priv.node.clone();
spawn(async move {
XvbNode::update_fastest_node(&client_http_c, &gui_api_c, &process_c).await;
if let Err(err) = PrivXmrigApi::update_xmrig_config(
&client_http_c,
XMRIG_CONFIG_URI,
&token_xmrig,
&node,
&address,
gui_api_xmrig_c,
)
.await
{
// show to console error about updating xmrig config
if let Err(e) = writeln!(
lock!(&gui_api_c).output,
"Failure to update xmrig config with HTTP API.\nError: {}",
err
) {
error!("XvB Watchdog | GUI status write failed: {}", e);
}
}
});
lock!(process).signal = ProcessSignal::None;
// the state will be Offline or Alive after update_fastest_node is done, meanwhile Signal will be None so not re-treated before update_fastest is done.
}
false
}
@ -745,7 +958,6 @@ mod test {
let client = hyper::Client::builder().build(https);
let new_data = thread::spawn(move || corr(client)).join().unwrap();
assert!(!new_data.reward_yearly.is_empty());
dbg!(new_data);
}
#[tokio::main]
async fn corr(client: Client<HttpsConnector<hyper::client::HttpConnector>>) -> XvbPubStats {

View file

@ -86,7 +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
pub const XMRIG_CONFIG_URI: &str = "http://127.0.0.1:18088/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";
@ -413,8 +413,10 @@ pub const XVB_HELP: &str = "You need to register an account by clicking on the l
pub const XVB_URL: &str = "https://xmrvsbeast.com";
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_NODE_PORT: &str = "4247";
pub const XVB_NODE_EU: &str = "eu.xmrvsbeast.com";
pub const XVB_NODE_NA: &str = "na.xmrvsbeast.com";
pub const XVB_NODE_RPC: &str = "18089";
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;