From ee2f27c5da23fa6268a8f2c696594b61e9d365b7 Mon Sep 17 00:00:00 2001 From: Cyrix126 <58007246+Cyrix126@users.noreply.github.com> Date: Tue, 31 Dec 2024 14:12:09 +0100 Subject: [PATCH] fix: green status p2pool when node offline (#59) * fix: green status p2pool when node offline P2Pool state will change dynamically based on: - Node connection, - p2p network connection, - synchronization fix https://github.com/Cyrix126/gupaxx/issues/57 --- src/helper/p2pool.rs | 130 ++++++++++++++++++++++++++++++++++++++--- src/helper/tests.rs | 3 +- src/utils/constants.rs | 4 ++ src/utils/regex.rs | 15 +++++ 4 files changed, 142 insertions(+), 10 deletions(-) diff --git a/src/helper/p2pool.rs b/src/helper/p2pool.rs index b67112d..5242c55 100644 --- a/src/helper/p2pool.rs +++ b/src/helper/p2pool.rs @@ -29,9 +29,11 @@ use crate::helper::signal_end; use crate::helper::sleep_end_loop; use crate::regex::P2POOL_REGEX; use crate::regex::contains_end_status; +use crate::regex::contains_newchain_tip; use crate::regex::contains_statuscommand; use crate::regex::contains_yourhashrate; use crate::regex::contains_yourshare; +use crate::regex::contains_zmq_failure; use crate::regex::estimated_hr; use crate::regex::nb_current_shares; use crate::{ @@ -222,13 +224,13 @@ impl Helper { ) { helper.lock().unwrap().p2pool.lock().unwrap().state = ProcessState::Middle; - let (args, api_path_local, api_path_network, api_path_pool) = + let (args, api_path_local, api_path_network, api_path_pool, api_path_p2p) = Self::build_p2pool_args_and_mutate_img(helper, state, path, backup_hosts); // Print arguments & user settings to console crate::disk::print_dash(&format!( - "P2Pool | Launch arguments: {:#?} | Local API Path: {:#?} | Network API Path: {:#?} | Pool API Path: {:#?}", - args, api_path_local, api_path_network, api_path_pool, + "P2Pool | Launch arguments: {:#?} | Local API Path: {:#?} | Network API Path: {:#?} | Pool API Path: {:#?} | P2P API Path {:#?}", + args, api_path_local, api_path_network, api_path_pool, api_path_p2p )); // Spawn watchdog thread @@ -247,6 +249,7 @@ impl Helper { api_path_local, api_path_network, api_path_pool, + api_path_p2p, gupax_p2pool_api, ); }); @@ -272,7 +275,7 @@ impl Helper { state: &P2pool, path: &Path, backup_hosts: Option>, - ) -> (Vec, PathBuf, PathBuf, PathBuf) { + ) -> (Vec, PathBuf, PathBuf, PathBuf, PathBuf) { let mut args = Vec::with_capacity(500); let path = path.to_path_buf(); let mut api_path = path; @@ -447,10 +450,18 @@ impl Helper { let mut api_path_local = api_path.clone(); let mut api_path_network = api_path.clone(); let mut api_path_pool = api_path.clone(); + let mut api_path_p2p = api_path.clone(); api_path_local.push(P2POOL_API_PATH_LOCAL); api_path_network.push(P2POOL_API_PATH_NETWORK); api_path_pool.push(P2POOL_API_PATH_POOL); - (args, api_path_local, api_path_network, api_path_pool) + api_path_p2p.push(P2POOL_API_PATH_P2P); + ( + args, + api_path_local, + api_path_network, + api_path_pool, + api_path_p2p, + ) } #[cold] @@ -469,6 +480,7 @@ impl Helper { api_path_local: std::path::PathBuf, api_path_network: std::path::PathBuf, api_path_pool: std::path::PathBuf, + api_path_p2p: std::path::PathBuf, gupax_p2pool_api: Arc>, ) { // 1a. Create PTY @@ -542,6 +554,26 @@ impl Helper { ), } } + debug!("P2Pool | Cleaning old [p2p] API files..."); + // Attempt to remove stale API file + match std::fs::remove_file(&api_path_p2p) { + Ok(_) => info!("P2Pool | Attempting to remove stale API file ... OK"), + Err(e) => warn!( + "P2Pool | Attempting to remove stale API file ... FAIL ... {}", + e + ), + } + // Attempt to create a default empty one. + if std::fs::File::create(&api_path_p2p).is_ok() { + let text = r#"{"connections":0,"incoming_connections":0,"peer_list_size":0,"peers":[],"uptime":0}"#; + match std::fs::write(&api_path_p2p, text) { + Ok(_) => info!("P2Pool | Creating default empty API file ... OK"), + Err(e) => warn!( + "P2Pool | Creating default empty API file ... FAIL ... {}", + e + ), + } + } let start = process.lock().unwrap().start; // Reset stats before loop @@ -608,6 +640,20 @@ impl Helper { PubP2poolApi::update_from_local(&mut pub_api_lock, local_api); } } + // Read [p2p] API + // allows to know if p2p is synced and connected to a Node. + debug!("P2Pool Watchdog | Attempting [p2p] API file read"); + if let Ok(string) = Self::path_to_string(&api_path_p2p, ProcessName::P2pool) { + // Deserialize + if let Ok(p2p_api) = PrivP2PoolP2PApi::from_str(&string) { + // Update the structs. + PubP2poolApi::update_from_p2p(&mut pub_api_lock, p2p_api); + } + } + + // check if state must be changed based on local and p2p API + pub_api_lock.update_state(&mut process_lock); + // If more than 1 minute has passed, read the other API files. let last_p2pool_request_expired = last_p2pool_request.elapsed() >= Duration::from_secs(60); @@ -746,7 +792,7 @@ pub struct PubP2poolApi { pub monero_difficulty: HumanNumber, // e.g: [15,000,000] pub monero_hashrate: HumanNumber, // e.g: [1.000 GH/s] pub hash: String, // Current block hash - pub height: HumanNumber, + pub height: u32, pub reward: AtomicUnit, // Pool API pub p2pool_difficulty: HumanNumber, @@ -763,6 +809,11 @@ pub struct PubP2poolApi { // from status pub sidechain_shares: u32, pub sidechain_ehr: f32, + // from height + pub synchronised: bool, + // from local/p2p + pub p2p_connected: u32, + pub node_connected: bool, } impl Default for PubP2poolApi { @@ -801,7 +852,7 @@ impl PubP2poolApi { monero_difficulty: HumanNumber::unknown(), monero_hashrate: HumanNumber::unknown(), hash: String::from("???"), - height: HumanNumber::unknown(), + height: 0, reward: AtomicUnit::new(), p2pool_difficulty: HumanNumber::unknown(), p2pool_hashrate: HumanNumber::unknown(), @@ -814,6 +865,9 @@ impl PubP2poolApi { user_monero_percent: HumanNumber::unknown(), sidechain_shares: 0, sidechain_ehr: 0.0, + p2p_connected: 0, + synchronised: false, + node_connected: false, } } @@ -876,7 +930,7 @@ impl PubP2poolApi { // 2. Parse the full STDOUT let mut output_parse = output_parse.lock().unwrap(); let (payouts_new, xmr_new) = Self::calc_payouts_and_xmr(&output_parse); - // Check for "SYNCHRONIZED" only if we aren't already. + // Check for "SYNCHRONIZED" only if we aren't already. Works at level 0 and above. if process.state == ProcessState::Syncing { // How many times the word was captured. let synchronized_captures = P2POOL_REGEX.synchronized.find_iter(&output_parse).count(); @@ -897,6 +951,15 @@ impl PubP2poolApi { // just finding 1 instance of "SYNCHRONIZED". process.state = ProcessState::Alive; } + // if the p2pool node was synced but is not anymore due to faulty monero node and is synced again, the status must be alive again + // required log level 2 minimum + if contains_newchain_tip(&output_parse) { + process.state = ProcessState::Alive; + } + } + // if the node is offline, p2pool can not function properly. Requires at least p2pool log level 1 + if process.state == ProcessState::Alive && contains_zmq_failure(&output_parse) { + process.state = ProcessState::Syncing; } // 3. Throw away [output_parse] @@ -976,6 +1039,15 @@ impl PubP2poolApi { ..std::mem::take(&mut *public) }; } + // Mutate [PubP2poolApi] with data from a [PrivP2PoolP2PApi] and the process output. + pub(super) fn update_from_p2p(public: &mut Self, p2p: PrivP2PoolP2PApi) { + *public = Self { + p2p_connected: p2p.connections, + // 30 seconds before concluding the monero node connection is lost + node_connected: p2p.zmq_last_active < 30, + ..std::mem::take(&mut *public) + }; + } // Mutate [PubP2poolApi] with data from a [PrivP2pool(Network|Pool)Api]. pub(super) fn update_from_network_pool( @@ -1033,7 +1105,7 @@ impl PubP2poolApi { monero_difficulty: HumanNumber::from_u64(monero_difficulty), monero_hashrate: HumanNumber::from_u64_to_gigahash_3_point(monero_hashrate), hash: net.hash, - height: HumanNumber::from_u32(net.height), + height: net.height, reward: AtomicUnit::from_u64(net.reward), p2pool_difficulty: HumanNumber::from_u64(p2pool_difficulty), p2pool_hashrate: HumanNumber::from_u64_to_megahash_3_point(p2pool_hashrate), @@ -1047,6 +1119,14 @@ impl PubP2poolApi { ..std::mem::take(&mut *public) }; } + /// Check if all conditions are met to be alive or if something is wrong + fn update_state(&self, process: &mut Process) { + if self.synchronised && self.node_connected && self.p2p_connected > 1 && self.height > 10 { + process.state = ProcessState::Alive; + } else { + process.state = ProcessState::Syncing; + } + } #[inline] pub fn calculate_share_or_block_time(hashrate: u64, difficulty: u64) -> HumanTime { @@ -1273,3 +1353,35 @@ impl PoolStatistics { } } } +//---------------------------------------------------------------------------------------------------- Private P2Pool "Network" API +// This matches P2Pool's [local/p2p] JSON API file. +#[derive(Debug, Serialize, Deserialize, Clone)] +pub(super) struct PrivP2PoolP2PApi { + pub connections: u32, + pub zmq_last_active: u32, +} + +impl Default for PrivP2PoolP2PApi { + fn default() -> Self { + Self::new() + } +} + +impl PrivP2PoolP2PApi { + fn new() -> Self { + Self { + connections: 0, + zmq_last_active: 0, + } + } + + pub(super) fn from_str(string: &str) -> std::result::Result { + match serde_json::from_str::(string) { + Ok(a) => Ok(a), + Err(e) => { + warn!("P2Pool Network API | Could not deserialize API data: {}", e); + Err(e) + } + } + } +} diff --git a/src/helper/tests.rs b/src/helper/tests.rs index b4309c9..9621bd5 100644 --- a/src/helper/tests.rs +++ b/src/helper/tests.rs @@ -25,6 +25,7 @@ mod test { p2pool::{PrivP2poolLocalApi, PrivP2poolNetworkApi}, xvb::{priv_stats::RuntimeDonationLevel, priv_stats::RuntimeMode}, }; + use crate::human::HumanNumber; use crate::miscs::client; #[test] @@ -308,7 +309,7 @@ Uptime = 0h 2m 4s assert_eq!(p.monero_difficulty.to_string(), "300,000,000,000"); assert_eq!(p.monero_hashrate.to_string(), "2.500 GH/s"); assert_eq!(p.hash.to_string(), "asdf"); - assert_eq!(p.height.to_string(), "1,234"); + assert_eq!(HumanNumber::from_u32(p.height).to_string(), "1,234"); assert_eq!(p.reward.to_u64(), 2345); assert_eq!(p.p2pool_difficulty.to_string(), "10,000,000"); assert_eq!(p.p2pool_hashrate.to_string(), "1.000 MH/s"); diff --git a/src/utils/constants.rs b/src/utils/constants.rs index afc5b6d..1787e8b 100644 --- a/src/utils/constants.rs +++ b/src/utils/constants.rs @@ -81,12 +81,16 @@ pub const P2POOL_API_PATH_LOCAL: &str = r"local\stratum"; pub const P2POOL_API_PATH_NETWORK: &str = r"network\stats"; #[cfg(target_os = "windows")] pub const P2POOL_API_PATH_POOL: &str = r"pool\stats"; +#[cfg(target_family = "windows")] +pub const P2POOL_API_PATH_P2P: &str = r"local\p2p"; #[cfg(target_family = "unix")] pub const P2POOL_API_PATH_LOCAL: &str = "local/stratum"; #[cfg(target_family = "unix")] pub const P2POOL_API_PATH_NETWORK: &str = "network/stats"; #[cfg(target_family = "unix")] pub const P2POOL_API_PATH_POOL: &str = "pool/stats"; +#[cfg(target_family = "unix")] +pub const P2POOL_API_PATH_P2P: &str = "local/p2p"; pub const XMRIG_API_SUMMARY_URI: &str = "1/summary"; // The default relative URI of XMRig's API summary // pub const XMRIG_API_CONFIG_URI: &str = "1/config"; // The default relative URI of XMRig's API config // todo allow user to change the port of the http api for xmrig and xmrig-proxy diff --git a/src/utils/regex.rs b/src/utils/regex.rs index 0a9da66..e25801b 100644 --- a/src/utils/regex.rs +++ b/src/utils/regex.rs @@ -277,6 +277,21 @@ pub fn contains_end_status(l: &str) -> bool { static LINE_SHARE: Lazy = Lazy::new(|| Regex::new(r"^Uptime ").unwrap()); LINE_SHARE.is_match(l) } +// P2Pool +/// if the node is disconnected +/// this error will be present if log > 1 and Node is disconnected +pub fn contains_zmq_failure(l: &str) -> bool { + static LINE_SHARE: Lazy = Lazy::new(|| { + Regex::new(r"(p2pool with offline node: failed: error Error (empty response)|ZMQReader failed to connect to|P2Pool Couldn't restart ZMQ reader: exception Operation cannot be accomplished in current state)").unwrap() + }); + LINE_SHARE.is_match(l) +} +/// a way to detect that p2pool is alive +pub fn contains_newchain_tip(l: &str) -> bool { + static LINE_SHARE: Lazy = Lazy::new(|| Regex::new(r"new chain tip").unwrap()); + LINE_SHARE.is_match(l) +} + //---------------------------------------------------------------------------------------------------- TEST #[cfg(test)] mod test {