diff --git a/package-lock.json b/package-lock.json index e5e43a3b7..764acb6d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "tari-universe", - "version": "0.8.31", + "version": "0.8.34", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "tari-universe", - "version": "0.8.31", + "version": "0.8.34", "dependencies": { "@floating-ui/react": "^0.26.28", "@lottiefiles/dotlottie-react": "^0.10.1", diff --git a/package.json b/package.json index 9c656716a..079ff6e7f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "tari-universe", "private": true, - "version": "0.8.31", + "version": "0.8.34", "type": "module", "scripts": { "dev": "vite dev --mode development", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 38cb66ce8..707bf3957 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3903,8 +3903,8 @@ dependencies = [ [[package]] name = "minotari_app_grpc" -version = "1.9.1-pre.0" -source = "git+https://github.com/tari-project/tari.git?rev=37c30be#37c30be6bd6df026dc16eec5f4d8cfaf2225f9c6" +version = "1.9.1-rc.1" +source = "git+https://github.com/tari-project/tari.git?tag=v1.9.1-rc.1#65a61008a5f7c05c34453535894b9a24132d2665" dependencies = [ "argon2", "base64 0.13.1", @@ -3933,8 +3933,8 @@ dependencies = [ [[package]] name = "minotari_ledger_wallet_common" -version = "1.9.1-pre.0" -source = "git+https://github.com/tari-project/tari.git?rev=37c30be#37c30be6bd6df026dc16eec5f4d8cfaf2225f9c6" +version = "1.9.1-rc.1" +source = "git+https://github.com/tari-project/tari.git?tag=v1.9.1-rc.1#65a61008a5f7c05c34453535894b9a24132d2665" dependencies = [ "bs58 0.5.1", ] @@ -3942,7 +3942,7 @@ dependencies = [ [[package]] name = "minotari_node_grpc_client" version = "0.1.0" -source = "git+https://github.com/tari-project/tari.git?rev=37c30be#37c30be6bd6df026dc16eec5f4d8cfaf2225f9c6" +source = "git+https://github.com/tari-project/tari.git?tag=v1.9.1-rc.1#65a61008a5f7c05c34453535894b9a24132d2665" dependencies = [ "minotari_app_grpc", ] @@ -3950,7 +3950,7 @@ dependencies = [ [[package]] name = "minotari_wallet_grpc_client" version = "0.1.0" -source = "git+https://github.com/tari-project/tari.git?rev=37c30be#37c30be6bd6df026dc16eec5f4d8cfaf2225f9c6" +source = "git+https://github.com/tari-project/tari.git?tag=v1.9.1-rc.1#65a61008a5f7c05c34453535894b9a24132d2665" dependencies = [ "minotari_app_grpc", "tari_common_types", @@ -6899,7 +6899,7 @@ dependencies = [ [[package]] name = "tari-universe" -version = "0.8.31" +version = "0.8.34" dependencies = [ "anyhow", "async-trait", @@ -6993,8 +6993,8 @@ dependencies = [ [[package]] name = "tari_common" -version = "1.9.1-pre.0" -source = "git+https://github.com/tari-project/tari.git?rev=37c30be#37c30be6bd6df026dc16eec5f4d8cfaf2225f9c6" +version = "1.9.1-rc.1" +source = "git+https://github.com/tari-project/tari.git?tag=v1.9.1-rc.1#65a61008a5f7c05c34453535894b9a24132d2665" dependencies = [ "anyhow", "config", @@ -7017,8 +7017,8 @@ dependencies = [ [[package]] name = "tari_common_sqlite" -version = "1.9.1-pre.0" -source = "git+https://github.com/tari-project/tari.git?rev=37c30be#37c30be6bd6df026dc16eec5f4d8cfaf2225f9c6" +version = "1.9.1-rc.1" +source = "git+https://github.com/tari-project/tari.git?tag=v1.9.1-rc.1#65a61008a5f7c05c34453535894b9a24132d2665" dependencies = [ "diesel", "diesel_migrations", @@ -7031,8 +7031,8 @@ dependencies = [ [[package]] name = "tari_common_types" -version = "1.9.1-pre.0" -source = "git+https://github.com/tari-project/tari.git?rev=37c30be#37c30be6bd6df026dc16eec5f4d8cfaf2225f9c6" +version = "1.9.1-rc.1" +source = "git+https://github.com/tari-project/tari.git?tag=v1.9.1-rc.1#65a61008a5f7c05c34453535894b9a24132d2665" dependencies = [ "base64 0.21.7", "bitflags 2.6.0", @@ -7057,8 +7057,8 @@ dependencies = [ [[package]] name = "tari_comms" -version = "1.9.1-pre.0" -source = "git+https://github.com/tari-project/tari.git?rev=37c30be#37c30be6bd6df026dc16eec5f4d8cfaf2225f9c6" +version = "1.9.1-rc.1" +source = "git+https://github.com/tari-project/tari.git?tag=v1.9.1-rc.1#65a61008a5f7c05c34453535894b9a24132d2665" dependencies = [ "anyhow", "async-trait", @@ -7101,8 +7101,8 @@ dependencies = [ [[package]] name = "tari_comms_dht" -version = "1.9.1-pre.0" -source = "git+https://github.com/tari-project/tari.git?rev=37c30be#37c30be6bd6df026dc16eec5f4d8cfaf2225f9c6" +version = "1.9.1-rc.1" +source = "git+https://github.com/tari-project/tari.git?tag=v1.9.1-rc.1#65a61008a5f7c05c34453535894b9a24132d2665" dependencies = [ "anyhow", "bitflags 2.6.0", @@ -7136,8 +7136,8 @@ dependencies = [ [[package]] name = "tari_comms_rpc_macros" -version = "1.9.1-pre.0" -source = "git+https://github.com/tari-project/tari.git?rev=37c30be#37c30be6bd6df026dc16eec5f4d8cfaf2225f9c6" +version = "1.9.1-rc.1" +source = "git+https://github.com/tari-project/tari.git?tag=v1.9.1-rc.1#65a61008a5f7c05c34453535894b9a24132d2665" dependencies = [ "proc-macro2", "quote", @@ -7146,8 +7146,8 @@ dependencies = [ [[package]] name = "tari_core" -version = "1.9.1-pre.0" -source = "git+https://github.com/tari-project/tari.git?rev=37c30be#37c30be6bd6df026dc16eec5f4d8cfaf2225f9c6" +version = "1.9.1-rc.1" +source = "git+https://github.com/tari-project/tari.git?tag=v1.9.1-rc.1#65a61008a5f7c05c34453535894b9a24132d2665" dependencies = [ "anyhow", "async-trait", @@ -7240,13 +7240,13 @@ dependencies = [ [[package]] name = "tari_features" -version = "1.9.1-pre.0" -source = "git+https://github.com/tari-project/tari.git?rev=37c30be#37c30be6bd6df026dc16eec5f4d8cfaf2225f9c6" +version = "1.9.1-rc.1" +source = "git+https://github.com/tari-project/tari.git?tag=v1.9.1-rc.1#65a61008a5f7c05c34453535894b9a24132d2665" [[package]] name = "tari_hashing" -version = "1.9.1-pre.0" -source = "git+https://github.com/tari-project/tari.git?rev=37c30be#37c30be6bd6df026dc16eec5f4d8cfaf2225f9c6" +version = "1.9.1-rc.1" +source = "git+https://github.com/tari-project/tari.git?tag=v1.9.1-rc.1#65a61008a5f7c05c34453535894b9a24132d2665" dependencies = [ "borsh", "digest", @@ -7255,8 +7255,8 @@ dependencies = [ [[package]] name = "tari_key_manager" -version = "1.9.1-pre.0" -source = "git+https://github.com/tari-project/tari.git?rev=37c30be#37c30be6bd6df026dc16eec5f4d8cfaf2225f9c6" +version = "1.9.1-rc.1" +source = "git+https://github.com/tari-project/tari.git?tag=v1.9.1-rc.1#65a61008a5f7c05c34453535894b9a24132d2665" dependencies = [ "argon2", "async-trait", @@ -7288,8 +7288,8 @@ dependencies = [ [[package]] name = "tari_max_size" -version = "1.9.1-pre.0" -source = "git+https://github.com/tari-project/tari.git?rev=37c30be#37c30be6bd6df026dc16eec5f4d8cfaf2225f9c6" +version = "1.9.1-rc.1" +source = "git+https://github.com/tari-project/tari.git?tag=v1.9.1-rc.1#65a61008a5f7c05c34453535894b9a24132d2665" dependencies = [ "borsh", "serde", @@ -7299,8 +7299,8 @@ dependencies = [ [[package]] name = "tari_mmr" -version = "1.9.1-pre.0" -source = "git+https://github.com/tari-project/tari.git?rev=37c30be#37c30be6bd6df026dc16eec5f4d8cfaf2225f9c6" +version = "1.9.1-rc.1" +source = "git+https://github.com/tari-project/tari.git?tag=v1.9.1-rc.1#65a61008a5f7c05c34453535894b9a24132d2665" dependencies = [ "borsh", "digest", @@ -7313,8 +7313,8 @@ dependencies = [ [[package]] name = "tari_p2p" -version = "1.9.1-pre.0" -source = "git+https://github.com/tari-project/tari.git?rev=37c30be#37c30be6bd6df026dc16eec5f4d8cfaf2225f9c6" +version = "1.9.1-rc.1" +source = "git+https://github.com/tari-project/tari.git?tag=v1.9.1-rc.1#65a61008a5f7c05c34453535894b9a24132d2665" dependencies = [ "anyhow", "fs2", @@ -7345,8 +7345,8 @@ dependencies = [ [[package]] name = "tari_script" -version = "1.9.1-pre.0" -source = "git+https://github.com/tari-project/tari.git?rev=37c30be#37c30be6bd6df026dc16eec5f4d8cfaf2225f9c6" +version = "1.9.1-rc.1" +source = "git+https://github.com/tari-project/tari.git?tag=v1.9.1-rc.1#65a61008a5f7c05c34453535894b9a24132d2665" dependencies = [ "blake2", "borsh", @@ -7363,8 +7363,8 @@ dependencies = [ [[package]] name = "tari_service_framework" -version = "1.9.1-pre.0" -source = "git+https://github.com/tari-project/tari.git?rev=37c30be#37c30be6bd6df026dc16eec5f4d8cfaf2225f9c6" +version = "1.9.1-rc.1" +source = "git+https://github.com/tari-project/tari.git?tag=v1.9.1-rc.1#65a61008a5f7c05c34453535894b9a24132d2665" dependencies = [ "anyhow", "async-trait", @@ -7378,16 +7378,16 @@ dependencies = [ [[package]] name = "tari_shutdown" -version = "1.9.1-pre.0" -source = "git+https://github.com/tari-project/tari.git?rev=37c30be#37c30be6bd6df026dc16eec5f4d8cfaf2225f9c6" +version = "1.9.1-rc.1" +source = "git+https://github.com/tari-project/tari.git?tag=v1.9.1-rc.1#65a61008a5f7c05c34453535894b9a24132d2665" dependencies = [ "futures 0.3.31", ] [[package]] name = "tari_storage" -version = "1.9.1-pre.0" -source = "git+https://github.com/tari-project/tari.git?rev=37c30be#37c30be6bd6df026dc16eec5f4d8cfaf2225f9c6" +version = "1.9.1-rc.1" +source = "git+https://github.com/tari-project/tari.git?tag=v1.9.1-rc.1#65a61008a5f7c05c34453535894b9a24132d2665" dependencies = [ "bincode", "lmdb-zero", @@ -7398,8 +7398,8 @@ dependencies = [ [[package]] name = "tari_test_utils" -version = "1.9.1-pre.0" -source = "git+https://github.com/tari-project/tari.git?rev=37c30be#37c30be6bd6df026dc16eec5f4d8cfaf2225f9c6" +version = "1.9.1-rc.1" +source = "git+https://github.com/tari-project/tari.git?tag=v1.9.1-rc.1#65a61008a5f7c05c34453535894b9a24132d2665" dependencies = [ "futures 0.3.31", "rand 0.8.5", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 9e05658db..35226e421 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -4,7 +4,7 @@ description = "Tari Universe" edition = "2021" name = "tari-universe" repository = "https://github.com/tari-project/universe" -version = "0.8.31" +version = "0.8.34" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -38,8 +38,8 @@ libsqlite3-sys = { version = "0.25.1", features = [ ] } # Required for tari_wallet log = "0.4.22" log4rs = "1.3.0" -minotari_node_grpc_client = { git = "https://github.com/tari-project/tari.git", rev = "37c30be" } -minotari_wallet_grpc_client = { git = "https://github.com/tari-project/tari.git", rev = "37c30be" } +minotari_node_grpc_client = { git = "https://github.com/tari-project/tari.git", tag = "v1.9.1-rc.1" } +minotari_wallet_grpc_client = { git = "https://github.com/tari-project/tari.git", tag = "v1.9.1-rc.1" } monero-address-creator = { git = "https://github.com/tari-project/monero-address-creator.git", rev = "6129ca0" } nix = { version = "0.29.0", features = ["signal"] } nvml-wrapper = "0.10.0" @@ -58,15 +58,15 @@ sha2 = "0.10.8" sys-locale = "0.3.1" sysinfo = "0.31.2" tar = "0.4.26" -tari_common = { git = "https://github.com/tari-project/tari.git", rev = "37c30be" } -tari_common_types = { git = "https://github.com/tari-project/tari.git", rev = "37c30be" } -tari_core = { git = "https://github.com/tari-project/tari.git", rev = "37c30be", features = [ +tari_common = { git = "https://github.com/tari-project/tari.git", tag = "v1.9.1-rc.1" } +tari_common_types = { git = "https://github.com/tari-project/tari.git", tag = "v1.9.1-rc.1" } +tari_core = { git = "https://github.com/tari-project/tari.git", tag = "v1.9.1-rc.1", features = [ "transactions", ] } tauri-plugin-single-instance = { git = "https://github.com/tari-project/tauri-plugins-workspace", rev = "09f29b0abe2cb1eb81365b65a3aa3f73325e4e17" } tari_crypto = "0.21.0" -tari_key_manager = { git = "https://github.com/tari-project/tari.git", rev = "37c30be" } -tari_shutdown = { git = "https://github.com/tari-project/tari.git", rev = "37c30be" } +tari_key_manager = { git = "https://github.com/tari-project/tari.git", tag = "v1.9.1-rc.1" } +tari_shutdown = { git = "https://github.com/tari-project/tari.git", tag = "v1.9.1-rc.1" } tari_utilities = "0.8.0" tauri = { git = "https://github.com/tari-project/tauri.git", rev = "67a06c8a9bae94f412b8059dfa8b4d8dd8ea0a25", features = [ "macos-private-api", diff --git a/src-tauri/binaries_versions_esmeralda.json b/src-tauri/binaries_versions_esmeralda.json index ea9990422..14f14a8b7 100644 --- a/src-tauri/binaries_versions_esmeralda.json +++ b/src-tauri/binaries_versions_esmeralda.json @@ -1,10 +1,10 @@ { "binaries": { "xmrig": "=6.22.0", - "mmproxy": "=1.9.1-pre.0", - "minotari_node": "=1.9.1-pre.0", - "wallet": "=1.9.1-pre.0", - "sha-p2pool": "=0.15.4", + "mmproxy": "=1.9.1-pre.1", + "minotari_node": "=1.9.1-pre.1", + "wallet": "=1.9.1-pre.1", + "sha-p2pool": "=0.16.2", "xtrgpuminer": "=0.2.10", "tor": "=13.5.7" } diff --git a/src-tauri/binaries_versions_nextnet.json b/src-tauri/binaries_versions_nextnet.json index 3ce5a4b90..d35160105 100644 --- a/src-tauri/binaries_versions_nextnet.json +++ b/src-tauri/binaries_versions_nextnet.json @@ -1,10 +1,10 @@ { "binaries": { "xmrig": "=6.22.0", - "mmproxy": "=1.9.1-rc.0", - "minotari_node": "=1.9.1-rc.0", - "wallet": "=1.9.1-rc.0", - "sha-p2pool": "=0.15.4", + "mmproxy": "=1.9.1-rc.1", + "minotari_node": "=1.9.1-rc.1", + "wallet": "=1.9.1-rc.1", + "sha-p2pool": "=0.16.2", "xtrgpuminer": "=0.2.10", "tor": "=13.5.7" } diff --git a/src-tauri/src/binaries/binaries_manager.rs b/src-tauri/src/binaries/binaries_manager.rs index 6ea44eedb..a898c478a 100644 --- a/src-tauri/src/binaries/binaries_manager.rs +++ b/src-tauri/src/binaries/binaries_manager.rs @@ -36,7 +36,7 @@ use super::{ Binaries, }; -use log::{error, info, warn}; +use log::{debug, error, info, warn}; pub const LOG_TARGET: &str = "tari::universe::binary_manager"; @@ -102,7 +102,7 @@ impl BinaryManager { VersionReq::default() }); - info!(target: LOG_TARGET, "Version requirements for {:?}: {:?}", binary_name, version_requirement); + debug!(target: LOG_TARGET, "Version requirements for {:?}: {:?}", binary_name, version_requirement); version_requirement } @@ -514,7 +514,7 @@ impl BinaryManager { } pub async fn read_local_versions(&mut self) { - info!(target: LOG_TARGET,"Reading local versions for binary: {:?}", self.binary_name); + debug!(target: LOG_TARGET,"Reading local versions for binary: {:?}", self.binary_name); let binary_folder = match self.adapter.get_binary_folder() { Ok(path) => path, diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 3355b29f5..586bf330f 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -33,14 +33,13 @@ use crate::external_dependencies::{ use crate::gpu_miner_adapter::{GpuMinerStatus, GpuNodeSource}; use crate::hardware::hardware_status_monitor::{HardwareStatusMonitor, PublicDeviceProperties}; use crate::internal_wallet::{InternalWallet, PaperWalletConfig}; -use crate::node_manager::NodeManagerError; use crate::p2pool::models::{Connections, Stats}; use crate::progress_tracker::ProgressTracker; use crate::tor_adapter::TorConfig; use crate::utils::shutdown_utils::stop_all_processes; use crate::wallet_adapter::{TransactionInfo, WalletBalance}; use crate::wallet_manager::WalletManagerError; -use crate::{setup_inner, UniverseAppState, APPLICATION_FOLDER_ID}; +use crate::{node_adapter, setup_inner, UniverseAppState, APPLICATION_FOLDER_ID}; use base64::prelude::*; use keyring::Entry; @@ -54,7 +53,6 @@ use std::sync::atomic::Ordering; use std::thread::{available_parallelism, sleep}; use std::time::{Duration, Instant, SystemTime}; use tari_common::configuration::Network; -use tari_core::transactions::tari_amount::MicroMinotari; use tauri::{Manager, PhysicalPosition, PhysicalSize}; use tauri_plugin_sentry::sentry; use tauri_plugin_sentry::sentry::protocol::Event; @@ -398,18 +396,27 @@ pub async fn get_miner_metrics( } state.is_getting_miner_metrics.store(true, Ordering::SeqCst); - let (sha_hash_rate, randomx_hash_rate, block_reward, block_height, block_time, is_synced) = state.node_manager - .get_network_hash_rate_and_block_reward().await - .unwrap_or_else(|e| { - if !matches!(e, NodeManagerError::NodeNotStarted) { - warn!(target: LOG_TARGET, "Error getting network hash rate and block reward: {}", e); - } - (0, 0, MicroMinotari(0), 0, 0, false) - }); + let node_status = state.base_node_latest_status.borrow().clone(); + let node_adapter::BaseNodeStatus { + sha_network_hashrate, + randomx_network_hashrate, + block_height, + block_time, + is_synced, + block_reward, + } = node_status; + // let (sha_hash_rate, randomx_hash_rate, block_reward, block_height, block_time, is_synced) = state.node_manager + // .get_network_hash_rate_and_block_reward().await + // .unwrap_or_else(|e| { + // if !matches!(e, NodeManagerError::NodeNotStarted) { + // warn!(target: LOG_TARGET, "Error getting network hash rate and block reward: {}", e); + // } + // (0, 0, MicroMinotari(0), 0, 0, false) + // }); let cpu_miner = state.cpu_miner.read().await; let cpu_mining_status = match cpu_miner - .status(randomx_hash_rate, block_reward) + .status(randomx_network_hashrate, block_reward) .await .map_err(|e| e.to_string()) { @@ -424,18 +431,7 @@ pub async fn get_miner_metrics( }; drop(cpu_miner); - let gpu_miner = state.gpu_miner.read().await; - let gpu_mining_status = match gpu_miner.status(sha_hash_rate, block_reward).await { - Ok(gpu) => gpu, - Err(e) => { - warn!(target: LOG_TARGET, "Error getting gpu miner status: {:?}", e); - state - .is_getting_miner_metrics - .store(false, Ordering::SeqCst); - return Err(e.to_string()); - } - }; - drop(gpu_miner); + let gpu_mining_status = state.gpu_latest_status.borrow().clone(); let gpu_public_parameters = HardwareStatusMonitor::current() .get_gpu_devices_public_properties() @@ -453,8 +449,8 @@ pub async fn get_miner_metrics( } let metrics_ret = MinerMetrics { - sha_network_hash_rate: sha_hash_rate, - randomx_network_hash_rate: randomx_hash_rate, + sha_network_hash_rate: sha_network_hashrate, + randomx_network_hash_rate: randomx_network_hashrate, cpu: CpuMinerMetrics { // hardware: cpu_public_parameters.clone(), mining: cpu_mining_status, @@ -670,43 +666,16 @@ pub async fn get_tari_wallet_details( state: tauri::State<'_, UniverseAppState>, ) -> Result { let timer = Instant::now(); - if state.is_getting_wallet_balance.load(Ordering::SeqCst) { - let read = state.cached_wallet_details.read().await; - if let Some(details) = &*read { - warn!(target: LOG_TARGET, "Already getting wallet balance, returning cached value"); - return Ok(details.clone()); - } - warn!(target: LOG_TARGET, "Already getting wallet balance"); - return Err("Already getting wallet balance".to_string()); - } - state - .is_getting_wallet_balance - .store(true, Ordering::SeqCst); - let wallet_balance = match state.wallet_manager.get_balance().await { - Ok(w) => Some(w), - Err(e) => { - if !matches!(e, WalletManagerError::WalletNotStarted) { - warn!(target: LOG_TARGET, "Error getting wallet balance: {}", e); - } - - None - } - }; let tari_address = state.tari_address.read().await; - - if timer.elapsed() > MAX_ACCEPTABLE_COMMAND_TIME { - warn!(target: LOG_TARGET, "get_tari_wallet_details took too long: {:?}", timer.elapsed()); - } + let wallet_balance = state.wallet_latest_balance.borrow().clone(); let result = TariWalletDetails { wallet_balance, tari_address_base58: tari_address.to_base58(), tari_address_emoji: tari_address.to_emoji_string(), }; - let mut lock = state.cached_wallet_details.write().await; - *lock = Some(result.clone()); - state - .is_getting_wallet_balance - .store(false, Ordering::SeqCst); + if timer.elapsed() > MAX_ACCEPTABLE_COMMAND_TIME { + warn!(target: LOG_TARGET, "get_tari_wallet_details took too long: {:?}", timer.elapsed()); + } Ok(result) } diff --git a/src-tauri/src/gpu_miner.rs b/src-tauri/src/gpu_miner.rs index f12767e78..6aa518af0 100644 --- a/src-tauri/src/gpu_miner.rs +++ b/src-tauri/src/gpu_miner.rs @@ -20,14 +20,13 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::{path::PathBuf, sync::Arc}; - use log::info; use serde::Deserialize; +use std::time::Duration; +use std::{path::PathBuf, sync::Arc}; use tari_common_types::tari_address::TariAddress; -use tari_core::transactions::tari_amount::MicroMinotari; use tari_shutdown::ShutdownSignal; -use tokio::sync::RwLock; +use tokio::sync::{watch, RwLock}; use crate::app_config::GpuThreads; use crate::binaries::{Binaries, BinaryResolver}; @@ -39,7 +38,6 @@ use crate::{ process_watcher::ProcessWatcher, }; -const SHA_BLOCKS_PER_DAY: u64 = 360; const LOG_TARGET: &str = "tari::universe::gpu_miner"; #[derive(Debug, Deserialize)] @@ -66,9 +64,12 @@ pub(crate) struct GpuMiner { } impl GpuMiner { - pub fn new() -> Self { - let adapter = GpuMinerAdapter::new(vec![]); - let process_watcher = ProcessWatcher::new(adapter); + pub fn new(status_broadcast: watch::Sender) -> Self { + let adapter = GpuMinerAdapter::new(vec![], status_broadcast); + let mut process_watcher = ProcessWatcher::new(adapter); + process_watcher.health_timeout = Duration::from_secs(9); + process_watcher.poll_time = Duration::from_secs(10); + Self { watcher: Arc::new(RwLock::new(process_watcher)), is_available: false, @@ -119,6 +120,13 @@ impl GpuMiner { pub async fn stop(&self) -> Result<(), anyhow::Error> { info!(target: LOG_TARGET, "Stopping xtrgpuminer"); let mut process_watcher = self.watcher.write().await; + let _res = process_watcher + .adapter + .latest_status_broadcast + .send(GpuMinerStatus { + is_mining: false, + ..GpuMinerStatus::default() + }); process_watcher.status_monitor = None; process_watcher.stop().await?; info!(target: LOG_TARGET, "xtrgpuminer stopped"); @@ -135,53 +143,6 @@ impl GpuMiner { lock.is_pid_file_exists(base_path) } - pub async fn status( - &self, - network_hash_rate: u64, - block_reward: MicroMinotari, - ) -> Result { - let process_watcher = self.watcher.read().await; - if !process_watcher.is_running() { - return Ok(GpuMinerStatus { - hash_rate: 0, - estimated_earnings: 0, - is_mining: false, - is_available: self.is_available, - }); - } - match &process_watcher.status_monitor { - Some(status_monitor) => { - let mut status = status_monitor.status().await?; - let hash_rate = status.hash_rate; - let estimated_earnings = if network_hash_rate == 0 { - 0 - } else { - #[allow(clippy::cast_possible_truncation)] - { - ((block_reward.as_u64() as f64) - * (hash_rate as f64 / network_hash_rate as f64) - * (SHA_BLOCKS_PER_DAY as f64)) - .floor() as u64 - } - }; - // Can't be more than the max reward for a day - let estimated_earnings = std::cmp::min( - estimated_earnings, - block_reward.as_u64() * SHA_BLOCKS_PER_DAY, - ); - status.estimated_earnings = estimated_earnings; - status.is_available = self.is_available; - Ok(status) - } - None => Ok(GpuMinerStatus { - hash_rate: 0, - estimated_earnings: 0, - is_mining: false, - is_available: self.is_available, - }), - } - } - pub async fn detect(&mut self, config_dir: PathBuf) -> Result<(), anyhow::Error> { info!(target: LOG_TARGET, "Verify if gpu miner can work on the system"); diff --git a/src-tauri/src/gpu_miner_adapter.rs b/src-tauri/src/gpu_miner_adapter.rs index 6dfa4a6bb..0ebe91dce 100644 --- a/src-tauri/src/gpu_miner_adapter.rs +++ b/src-tauri/src/gpu_miner_adapter.rs @@ -36,6 +36,7 @@ use std::time::Instant; use tari_common::configuration::Network; use tari_common_types::tari_address::TariAddress; use tari_shutdown::Shutdown; +use tokio::sync::watch; #[cfg(target_os = "windows")] use crate::utils::setup_utils::setup_utils::add_firewall_rule; @@ -60,10 +61,14 @@ pub(crate) struct GpuMinerAdapter { pub(crate) coinbase_extra: String, pub(crate) excluded_gpu_devices: Vec, pub(crate) gpu_devices: Vec, + pub(crate) latest_status_broadcast: watch::Sender, } impl GpuMinerAdapter { - pub fn new(gpu_devices: Vec) -> Self { + pub fn new( + gpu_devices: Vec, + latest_status_broadcast: watch::Sender, + ) -> Self { Self { tari_address: TariAddress::default(), gpu_grid_size: gpu_devices @@ -77,6 +82,7 @@ impl GpuMinerAdapter { coinbase_extra: "tari-universe".to_string(), excluded_gpu_devices: vec![], gpu_devices, + latest_status_broadcast, } } @@ -233,6 +239,7 @@ impl ProcessAdapter for GpuMinerAdapter { GpuMinerStatusMonitor { http_api_port, start_time: Instant::now(), + latest_status_broadcast: self.latest_status_broadcast.clone(), }, )) } @@ -250,12 +257,14 @@ impl ProcessAdapter for GpuMinerAdapter { pub struct GpuMinerStatusMonitor { http_api_port: u16, start_time: Instant, + latest_status_broadcast: watch::Sender, } #[async_trait] impl StatusMonitor for GpuMinerStatusMonitor { async fn check_health(&self) -> HealthStatus { if let Ok(status) = self.status().await { + let _result = self.latest_status_broadcast.send(status.clone()); // GPU returns 0 for first 10 seconds until it has an average if status.hash_rate > 0 || self.start_time.elapsed().as_secs() < 11 { HealthStatus::Healthy @@ -285,14 +294,12 @@ impl GpuMinerStatusMonitor { is_mining: false, hash_rate: 0, estimated_earnings: 0, - is_available: false, }); } return Ok(GpuMinerStatus { is_mining: false, hash_rate: 0, estimated_earnings: 0, - is_available: false, }); } }; @@ -305,7 +312,6 @@ impl GpuMinerStatusMonitor { is_mining: false, hash_rate: 0, estimated_earnings: 0, - is_available: false, }); } }; @@ -314,7 +320,6 @@ impl GpuMinerStatusMonitor { is_mining: true, estimated_earnings: 0, hash_rate: body.total_hashrate.ten_seconds.unwrap_or(0.0) as u64, - is_available: true, }) } } @@ -334,10 +339,9 @@ pub(crate) struct AverageHashrate { one_minute: Option, } -#[derive(Debug, Serialize, Clone)] +#[derive(Debug, Serialize, Clone, Default)] pub(crate) struct GpuMinerStatus { pub is_mining: bool, pub hash_rate: u64, pub estimated_earnings: u64, - pub is_available: bool, } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 6051064ae..9eeb7d0af 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -24,13 +24,16 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] use auto_launcher::AutoLauncher; +use gpu_miner_adapter::GpuMinerStatus; use hardware::hardware_status_monitor::HardwareStatusMonitor; use log::trace; use log::{debug, error, info, warn}; +use node_adapter::BaseNodeStatus; use p2pool::models::Connections; -use std::fs::{create_dir_all, remove_dir_all, remove_file, File}; +use std::fs::{remove_dir_all, remove_file}; use tokio::sync::watch::{self}; use updates_manager::UpdatesManager; +use wallet_adapter::WalletBalance; use log4rs::config::RawConfig; use serde::Serialize; @@ -60,7 +63,7 @@ use telemetry_manager::TelemetryManager; use crate::cpu_miner::CpuMiner; use crate::app_config::WindowSettings; -use crate::commands::{CpuMinerConnection, MinerMetrics, TariWalletDetails}; +use crate::commands::{CpuMinerConnection, MinerMetrics}; #[allow(unused_imports)] use crate::external_dependencies::ExternalDependencies; use crate::feedback::Feedback; @@ -562,7 +565,9 @@ async fn setup_inner( #[derive(Clone)] struct UniverseAppState { stop_start_mutex: Arc>, - is_getting_wallet_balance: Arc, + base_node_latest_status: Arc>, + wallet_latest_balance: Arc>>, + gpu_latest_status: Arc>, is_getting_p2pool_stats: Arc, is_getting_p2pool_connections: Arc, is_getting_miner_metrics: Arc, @@ -586,7 +591,6 @@ struct UniverseAppState { updates_manager: UpdatesManager, cached_p2pool_stats: Arc>>>, cached_p2pool_connections: Arc>>>, - cached_wallet_details: Arc>>, cached_miner_metrics: Arc>>, setup_counter: Arc>>, } @@ -618,8 +622,10 @@ fn main() { // NOTE: Nothing is started at this point, so ports are not known. You can only start settings ports // and addresses once the different services have been started. // A better way is to only provide the config when we start the service. - let node_manager = NodeManager::new(); - let wallet_manager = WalletManager::new(node_manager.clone()); + let (base_node_watch_tx, base_node_watch_rx) = watch::channel(BaseNodeStatus::default()); + let node_manager = NodeManager::new(base_node_watch_tx); + let (wallet_watch_tx, wallet_watch_rx) = watch::channel::>(None); + let wallet_manager = WalletManager::new(node_manager.clone(), wallet_watch_tx); let wallet_manager2 = wallet_manager.clone(); let p2pool_manager = P2poolManager::new(); @@ -636,20 +642,21 @@ fn main() { let app_in_memory_config = Arc::new(RwLock::new(app_in_memory_config::AppInMemoryConfig::init())); + let (gpu_status_tx, gpu_status_rx) = watch::channel(GpuMinerStatus::default()); let cpu_miner: Arc> = Arc::new(CpuMiner::new().into()); - let gpu_miner: Arc> = Arc::new(GpuMiner::new().into()); + let gpu_miner: Arc> = Arc::new(GpuMiner::new(gpu_status_tx).into()); let app_config_raw = AppConfig::new(); let app_config = Arc::new(RwLock::new(app_config_raw.clone())); let telemetry_manager: TelemetryManager = TelemetryManager::new( - node_manager.clone(), cpu_miner.clone(), - gpu_miner.clone(), app_config.clone(), app_in_memory_config.clone(), Some(Network::default()), p2pool_manager.clone(), + gpu_status_rx.clone(), + base_node_watch_rx.clone(), ); let updates_manager = UpdatesManager::new(app_config.clone(), shutdown.to_signal()); @@ -662,7 +669,9 @@ fn main() { is_getting_miner_metrics: Arc::new(AtomicBool::new(false)), is_getting_p2pool_stats: Arc::new(AtomicBool::new(false)), is_getting_p2pool_connections: Arc::new(AtomicBool::new(false)), - is_getting_wallet_balance: Arc::new(AtomicBool::new(false)), + base_node_latest_status: Arc::new(base_node_watch_rx), + wallet_latest_balance: Arc::new(wallet_watch_rx), + gpu_latest_status: Arc::new(gpu_status_rx), is_setup_finished: Arc::new(RwLock::new(false)), is_getting_transaction_history: Arc::new(AtomicBool::new(false)), config: app_config.clone(), @@ -683,7 +692,6 @@ fn main() { updates_manager, cached_p2pool_stats: Arc::new(RwLock::new(None)), cached_p2pool_connections: Arc::new(RwLock::new(None)), - cached_wallet_details: Arc::new(RwLock::new(None)), cached_miner_metrics: Arc::new(RwLock::new(None)), setup_counter: Arc::new(RwLock::new(AutoRollback::new(false))), }; @@ -712,14 +720,8 @@ fn main() { // Remove this after it's been rolled out for a few versions let log_path = app.path().app_log_dir().map_err(|e| e.to_string())?; let logs_cleared_file = log_path.join("logs_cleared"); - if !logs_cleared_file.exists() { - match remove_dir_all(&log_path) { - Ok(()) => { - create_dir_all(&log_path).map_err(|e| e.to_string())?; - File::create(&logs_cleared_file).map_err(|e| e.to_string())?; - }, - Err(e) => warn!(target: LOG_TARGET, "Could not clear log folder: {}", e) - } + if logs_cleared_file.exists() { + remove_file(&logs_cleared_file).map_err(|e| e.to_string())?; } let contents = setup_logging( diff --git a/src-tauri/src/node_adapter.rs b/src-tauri/src/node_adapter.rs index 24169aafc..5802c6aae 100644 --- a/src-tauri/src/node_adapter.rs +++ b/src-tauri/src/node_adapter.rs @@ -30,7 +30,7 @@ use crate::utils::logging_utils::setup_logging; use crate::ProgressTracker; use anyhow::{anyhow, Error}; use async_trait::async_trait; -use log::info; +use log::{info, warn}; use minotari_node_grpc_client::grpc::{ BlockHeader, Empty, GetBlocksRequest, HeightRequest, NewBlockTemplateRequest, Peer, PowAlgo, SyncState, @@ -44,6 +44,7 @@ use tari_core::transactions::tari_amount::MicroMinotari; use tari_crypto::ristretto::RistrettoPublicKey; use tari_shutdown::{Shutdown, ShutdownSignal}; use tari_utilities::ByteArray; +use tokio::sync::watch; #[cfg(target_os = "windows")] use crate::utils::setup_utils::setup_utils::add_firewall_rule; @@ -57,10 +58,11 @@ pub(crate) struct MinotariNodeAdapter { pub(crate) use_pruned_mode: bool, pub(crate) tor_control_port: Option, required_initial_peers: u32, + latest_status_broadcast: watch::Sender, } impl MinotariNodeAdapter { - pub fn new() -> Self { + pub fn new(status_broadcast: watch::Sender) -> Self { let port = PortAllocator::new().assign_port_with_fallback(); let tcp_listener_port = PortAllocator::new().assign_port_with_fallback(); Self { @@ -70,6 +72,7 @@ impl MinotariNodeAdapter { required_initial_peers: 3, use_tor: false, tor_control_port: None, + latest_status_broadcast: status_broadcast, } } } @@ -209,9 +212,7 @@ impl ProcessAdapter for MinotariNodeAdapter { grpc_port: self.grpc_port, required_sync_peers: self.required_initial_peers, shutdown_signal: status_shutdown, - last_block_height: 0, - last_sha3_estimated_hashrate: 0, - last_randomx_estimated_hashrate: 0, + latest_status_broadcast: self.latest_status_broadcast.clone(), }, )) } @@ -233,52 +234,77 @@ pub enum MinotariNodeStatusMonitorError { NodeNotStarted, } +#[derive(Clone, Debug, Default)] +pub(crate) struct BaseNodeStatus { + pub sha_network_hashrate: u64, + pub randomx_network_hashrate: u64, + pub block_reward: MicroMinotari, + pub block_height: u64, + pub block_time: u64, + pub is_synced: bool, +} + #[derive(Clone)] pub struct MinotariNodeStatusMonitor { grpc_port: u16, required_sync_peers: u32, shutdown_signal: ShutdownSignal, - last_block_height: u64, - last_sha3_estimated_hashrate: u64, - last_randomx_estimated_hashrate: u64, + latest_status_broadcast: watch::Sender, } #[async_trait] impl StatusMonitor for MinotariNodeStatusMonitor { async fn check_health(&self) -> HealthStatus { - if self.get_identity().await.is_ok() { - HealthStatus::Healthy - } else { - HealthStatus::Unhealthy + match self.get_network_hash_rate_and_block_reward().await { + Ok(res) => { + let _res = self.latest_status_broadcast.send(res); + HealthStatus::Healthy + } + Err(e) => { + warn!(target: LOG_TARGET, "Error checking base node health: {:?}", e); + HealthStatus::Unhealthy + } } } } impl MinotariNodeStatusMonitor { pub async fn get_network_hash_rate_and_block_reward( - &mut self, - ) -> Result<(u64, u64, MicroMinotari, u64, u64, bool), MinotariNodeStatusMonitorError> { - // TODO: use GRPC port returned from process + &self, + ) -> Result { let mut client = BaseNodeGrpcClient::connect(format!("http://127.0.0.1:{}", self.grpc_port)) .await .map_err(|_| MinotariNodeStatusMonitorError::NodeNotStarted)?; - let res = client - .get_new_block_template(NewBlockTemplateRequest { - algo: Some(PowAlgo { pow_algo: 1 }), - max_weight: 0, - }) - .await - .map_err(|e| MinotariNodeStatusMonitorError::UnknownError(e.into()))?; - let res = res.into_inner(); + let mut reward = 0; + // The base node returns a stupid error if the template is out of sync, so try multiple times + let max_template_retries = 5; - let reward = res - .miner_data - .ok_or_else(|| { - MinotariNodeStatusMonitorError::UnknownError(anyhow!("No miner data found")) - })? - .reward; + for _ in 0..max_template_retries { + let res = match client + .get_new_block_template(NewBlockTemplateRequest { + algo: Some(PowAlgo { pow_algo: 1 }), + max_weight: 0, + }) + .await + { + Ok(r) => r, + Err(e) => { + warn!(target: LOG_TARGET, "Failed to get new block template: {}", e); + continue; + } + }; + let res = res.into_inner(); + + reward = res + .miner_data + .ok_or_else(|| { + MinotariNodeStatusMonitorError::UnknownError(anyhow!("No miner data found")) + })? + .reward; + break; + } let res = client .get_tip_info(Empty {}) @@ -300,16 +326,16 @@ impl MinotariNodeStatusMonitor { metadata.timestamp, ); - if sync_achieved && (block_height <= self.last_block_height) { - return Ok(( - self.last_sha3_estimated_hashrate, - self.last_randomx_estimated_hashrate, - MicroMinotari(reward), - block_height, - block_time, - sync_achieved, - )); - } + // if sync_achieved && (block_height <= self.last_block_height) { + // return Ok(( + // self.last_sha3_estimated_hashrate, + // self.last_randomx_estimated_hashrate, + // MicroMinotari(reward), + // block_height, + // block_time, + // sync_achieved, + // )); + // } // First try with 10 blocks let blocks = [10, 100]; @@ -342,23 +368,20 @@ impl MinotariNodeStatusMonitor { last_randomx_estimated_hashrate = difficulty.randomx_estimated_hash_rate; } - result = Ok(( - last_sha3_estimated_hashrate, - last_randomx_estimated_hashrate, - MicroMinotari(reward), + result = Ok(BaseNodeStatus { + sha_network_hashrate: last_sha3_estimated_hashrate, + randomx_network_hashrate: last_randomx_estimated_hashrate, + block_reward: MicroMinotari(reward), block_height, block_time, - sync_achieved, - )); + is_synced: sync_achieved, + }); } if last_randomx_estimated_hashrate != 0 && last_sha3_estimated_hashrate != 0 { - self.last_sha3_estimated_hashrate = last_sha3_estimated_hashrate; - self.last_randomx_estimated_hashrate = last_randomx_estimated_hashrate; break; } } - self.last_block_height = block_height; Ok(result?) } @@ -406,16 +429,27 @@ impl MinotariNodeStatusMonitor { } #[allow(clippy::too_many_lines)] - pub async fn wait_synced(&self, progress_tracker: ProgressTracker) -> Result<(), Error> { + pub async fn wait_synced( + &self, + progress_tracker: ProgressTracker, + ) -> Result<(), MinotariNodeStatusMonitorError> { let mut client = - BaseNodeGrpcClient::connect(format!("http://127.0.0.1:{}", self.grpc_port)).await?; + BaseNodeGrpcClient::connect(format!("http://127.0.0.1:{}", self.grpc_port)) + .await + .map_err(|_e| MinotariNodeStatusMonitorError::NodeNotStarted)?; loop { if self.shutdown_signal.is_triggered() { break Ok(()); } - let tip = client.get_tip_info(Empty {}).await?; - let sync_progress = client.get_sync_progress(Empty {}).await?; + let tip = client + .get_tip_info(Empty {}) + .await + .map_err(|e| MinotariNodeStatusMonitorError::UnknownError(e.into()))?; + let sync_progress = client + .get_sync_progress(Empty {}) + .await + .map_err(|e| MinotariNodeStatusMonitorError::UnknownError(e.into()))?; let tip_res = tip.into_inner(); let sync_progress = sync_progress.into_inner(); if tip_res.initial_sync_achieved { diff --git a/src-tauri/src/node_manager.rs b/src-tauri/src/node_manager.rs index 1c4e0ea4e..2aff328db 100644 --- a/src-tauri/src/node_manager.rs +++ b/src-tauri/src/node_manager.rs @@ -29,17 +29,16 @@ use log::{error, info}; use minotari_node_grpc_client::grpc::Peer; use serde_json::json; use tari_common::configuration::Network; -use tari_core::transactions::tari_amount::MicroMinotari; use tari_crypto::ristretto::RistrettoPublicKey; use tari_shutdown::ShutdownSignal; use tari_utilities::hex::Hex; use tauri_plugin_sentry::sentry; use tauri_plugin_sentry::sentry::protocol::Event; use tokio::fs; -use tokio::sync::RwLock; +use tokio::sync::{watch, RwLock}; use crate::network_utils::{get_best_block_from_block_scan, get_block_info_from_block_scan}; -use crate::node_adapter::{MinotariNodeAdapter, MinotariNodeStatusMonitorError}; +use crate::node_adapter::{BaseNodeStatus, MinotariNodeAdapter, MinotariNodeStatusMonitorError}; use crate::process_watcher::ProcessWatcher; use crate::ProgressTracker; @@ -68,7 +67,7 @@ impl Clone for NodeManager { } impl NodeManager { - pub fn new() -> Self { + pub fn new(status_broadcast: watch::Sender) -> Self { // TODO: wire up to front end // let mut use_tor = true; @@ -78,9 +77,10 @@ impl NodeManager { // use_tor = false; // } - let adapter = MinotariNodeAdapter::new(); + let adapter = MinotariNodeAdapter::new(status_broadcast); let mut process_watcher = ProcessWatcher::new(adapter); - process_watcher.health_timeout = Duration::from_secs(10); + process_watcher.poll_time = Duration::from_secs(10); + process_watcher.health_timeout = Duration::from_secs(9); process_watcher.expected_startup_time = Duration::from_secs(120); Self { @@ -164,7 +164,19 @@ impl NodeManager { .status_monitor .as_ref() .ok_or_else(|| anyhow::anyhow!("wait_synced: Node not started"))?; - status_monitor.wait_synced(progress_tracker).await + loop { + match status_monitor.wait_synced(progress_tracker.clone()).await { + Ok(_) => return Ok(()), + Err(e) => match e { + MinotariNodeStatusMonitorError::NodeNotStarted => { + continue; + } + _ => { + return Err(NodeManagerError::UnknownError(e.into()).into()); + } + }, + } + } } pub async fn wait_ready(&self) -> Result<(), NodeManagerError> { @@ -198,27 +210,6 @@ impl NodeManager { Ok(0) } - /// Returns Sha hashrate, Rx hashrate and block reward - pub async fn get_network_hash_rate_and_block_reward( - &self, - ) -> Result<(u64, u64, MicroMinotari, u64, u64, bool), NodeManagerError> { - let mut status_monitor_lock = self.watcher.write().await; - let status_monitor = status_monitor_lock - .status_monitor - .as_mut() - .ok_or_else(|| NodeManagerError::NodeNotStarted)?; - status_monitor - .get_network_hash_rate_and_block_reward() - .await - .map_err(|e| { - if matches!(e, MinotariNodeStatusMonitorError::NodeNotStarted) { - NodeManagerError::NodeNotStarted - } else { - NodeManagerError::UnknownError(e.into()) - } - }) - } - pub async fn get_identity(&self) -> Result { let status_monitor_lock = self.watcher.read().await; let status_monitor = status_monitor_lock @@ -248,12 +239,16 @@ impl NodeManager { &self, report_to_sentry: bool, ) -> Result { - let mut status_monitor_lock = self.watcher.write().await; + let status_monitor_lock = self.watcher.read().await; let status_monitor = status_monitor_lock .status_monitor - .as_mut() - .ok_or_else(|| anyhow::anyhow!("Node not started"))?; - let (_, _, _, local_tip, _, is_synced) = status_monitor + .as_ref() + .ok_or_else(|| anyhow::anyhow!("check_if_is_orphan_chain: Node not started"))?; + let BaseNodeStatus { + is_synced, + block_height: local_tip, + .. + } = status_monitor .get_network_hash_rate_and_block_reward() .await .map_err(|e| { diff --git a/src-tauri/src/process_watcher.rs b/src-tauri/src/process_watcher.rs index 128ccd959..745710da2 100644 --- a/src-tauri/src/process_watcher.rs +++ b/src-tauri/src/process_watcher.rs @@ -38,6 +38,7 @@ pub struct ProcessWatcher { watcher_task: Option>>, internal_shutdown: Shutdown, pub poll_time: tokio::time::Duration, + /// Health timeout should always be less than poll time otherwise you will have overlapping calls pub health_timeout: tokio::time::Duration, pub expected_startup_time: tokio::time::Duration, pub(crate) status_monitor: Option, @@ -76,7 +77,6 @@ impl ProcessWatcher { log_path: PathBuf, binary: Binaries, ) -> Result<(), anyhow::Error> { - info!(target: LOG_TARGET, "App shutdown triggered or terminated status for {} = {} | {}", self.adapter.name(),app_shutdown.is_triggered(),app_shutdown.is_terminated()); if app_shutdown.is_terminated() || app_shutdown.is_triggered() { return Ok(()); } @@ -221,7 +221,7 @@ async fn do_health_check( } } HealthStatus::Unhealthy => { - error!(target: LOG_TARGET, "{} is not healthy. Health check returned false", name); + warn!(target: LOG_TARGET, "{} is not healthy. Health check returned false", name); } } } diff --git a/src-tauri/src/telemetry_manager.rs b/src-tauri/src/telemetry_manager.rs index 1aabff9b6..0639fd919 100644 --- a/src-tauri/src/telemetry_manager.rs +++ b/src-tauri/src/telemetry_manager.rs @@ -21,13 +21,13 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::app_in_memory_config::AppInMemoryConfig; +use crate::gpu_miner_adapter::GpuMinerStatus; use crate::hardware::hardware_status_monitor::HardwareStatusMonitor; +use crate::node_adapter::BaseNodeStatus; use crate::p2pool_manager::{self, P2poolManager}; use crate::{ app_config::{AppConfig, MiningMode}, cpu_miner::CpuMiner, - gpu_miner::GpuMiner, - node_manager::NodeManager, }; use anyhow::Result; use base64::prelude::*; @@ -46,10 +46,9 @@ use std::ops::Div; use std::pin::Pin; use std::{sync::Arc, thread::sleep, time::Duration}; use tari_common::configuration::Network; -use tari_core::transactions::tari_amount::MicroMinotari; use tari_utilities::encoding::MBase58; use tauri::Emitter; -use tokio::sync::RwLock; +use tokio::sync::{watch, RwLock}; use tokio_util::sync::CancellationToken; const LOG_TARGET: &str = "tari::universe::telemetry_manager"; @@ -199,38 +198,38 @@ pub struct TelemetryData { } pub struct TelemetryManager { - node_manager: NodeManager, cpu_miner: Arc>, - gpu_miner: Arc>, config: Arc>, in_memory_config: Arc>, pub cancellation_token: CancellationToken, node_network: Option, p2pool_manager: P2poolManager, airdrop_access_token: Arc>>, + gpu_status: watch::Receiver, + node_status: watch::Receiver, } impl TelemetryManager { pub fn new( - node_manager: NodeManager, cpu_miner: Arc>, - gpu_miner: Arc>, config: Arc>, in_memory_config: Arc>, network: Option, p2pool_manager: P2poolManager, + gpu_status: watch::Receiver, + node_status: watch::Receiver, ) -> Self { let cancellation_token = CancellationToken::new(); Self { - node_manager, cpu_miner, - gpu_miner, config, cancellation_token, node_network: network, in_memory_config, p2pool_manager, airdrop_access_token: Arc::new(RwLock::new(None)), + gpu_status, + node_status, } } @@ -296,9 +295,9 @@ impl TelemetryManager { timeout: Duration, window: tauri::Window, ) -> Result<(), TelemetryManagerError> { - let node_manager = self.node_manager.clone(); let cpu_miner = self.cpu_miner.clone(); - let gpu_miner = self.gpu_miner.clone(); + let gpu_status = self.gpu_status.clone(); + let node_status = self.node_status.clone(); let config = self.config.clone(); let cancellation_token: CancellationToken = self.cancellation_token.clone(); let network = self.node_network; @@ -314,7 +313,7 @@ impl TelemetryManager { let telemetry_collection_enabled = config_cloned.read().await.allow_telemetry(); if telemetry_collection_enabled { let airdrop_access_token_validated = validate_jwt(airdrop_access_token.clone()).await; - let telemetry_data = get_telemetry_data(cpu_miner.clone(), gpu_miner.clone(), node_manager.clone(), p2pool_manager_cloned.clone(), config.clone(), network).await; + let telemetry_data = get_telemetry_data(&cpu_miner, &gpu_status, &node_status, &p2pool_manager_cloned, &config, network).await; let airdrop_api_url = in_memory_config_cloned.read().await.airdrop_api_url.clone(); handle_telemetry_data(telemetry_data, airdrop_api_url, airdrop_access_token_validated, window.clone()).await; } @@ -370,40 +369,33 @@ fn decode_jwt_claims(t: &str) -> Option { #[allow(clippy::too_many_lines)] async fn get_telemetry_data( - cpu_miner: Arc>, - gpu_miner: Arc>, - node_manager: NodeManager, - p2pool_manager: p2pool_manager::P2poolManager, - config: Arc>, + cpu_miner: &RwLock, + gpu_latest_miner_stats: &watch::Receiver, + node_latest_status: &watch::Receiver, + p2pool_manager: &p2pool_manager::P2poolManager, + config: &RwLock, network: Option, ) -> Result { - let (sha_hash_rate, randomx_hash_rate, block_reward, block_height, _block_time, is_synced) = - node_manager - .get_network_hash_rate_and_block_reward() - .await - .unwrap_or((0, 0, MicroMinotari(0), 0, 0, false)); + let BaseNodeStatus { + randomx_network_hashrate, + block_reward, + block_height, + is_synced, + .. + } = node_latest_status.borrow().clone(); let cpu_miner = cpu_miner.read().await; - let cpu = match cpu_miner.status(randomx_hash_rate, block_reward).await { + let cpu = match cpu_miner + .status(randomx_network_hashrate, block_reward) + .await + { Ok(cpu) => cpu, Err(e) => { warn!(target: LOG_TARGET, "Error getting cpu miner status: {:?}", e); return Err(TelemetryManagerError::Other(e)); } }; - let gpu_miner_lock = gpu_miner.read().await; - let gpu_status = match gpu_miner_lock.status(sha_hash_rate, block_reward).await { - Ok(gpu) => gpu, - Err(e) => { - warn!(target: LOG_TARGET, "Error getting gpu miner status: {:?}", e); - return Err(TelemetryManagerError::Other(e)); - } - }; - - // let hardware_status = HardwareMonitor::current() - // .write() - // .await - // .read_hardware_parameters(); + let gpu_status = gpu_latest_miner_stats.borrow().clone(); let gpu_hardware_parameters = HardwareStatusMonitor::current() .get_gpu_public_properties() diff --git a/src-tauri/src/wallet_adapter.rs b/src-tauri/src/wallet_adapter.rs index 40e0f6d99..cdb8638d6 100644 --- a/src-tauri/src/wallet_adapter.rs +++ b/src-tauri/src/wallet_adapter.rs @@ -39,6 +39,7 @@ use tari_core::transactions::tari_amount::MicroMinotari; use tari_crypto::ristretto::RistrettoPublicKey; use tari_shutdown::Shutdown; use tari_utilities::hex::Hex; +use tokio::sync::watch; #[cfg(target_os = "windows")] use crate::utils::setup_utils::setup_utils::add_firewall_rule; @@ -53,10 +54,11 @@ pub struct WalletAdapter { pub(crate) spend_key: String, pub(crate) tcp_listener_port: u16, pub(crate) grpc_port: u16, + balance_broadcast: watch::Sender>, } impl WalletAdapter { - pub fn new(use_tor: bool) -> Self { + pub fn new(use_tor: bool, balance_broadcast: watch::Sender>) -> Self { let tcp_listener_port = PortAllocator::new().assign_port_with_fallback(); let grpc_port = PortAllocator::new().assign_port_with_fallback(); Self { @@ -67,6 +69,7 @@ impl WalletAdapter { spend_key: "".to_string(), tcp_listener_port, grpc_port, + balance_broadcast, } } } @@ -196,6 +199,7 @@ impl ProcessAdapter for WalletAdapter { }, WalletStatusMonitor { grpc_port: self.grpc_port, + latest_balance_broadcast: self.balance_broadcast.clone(), }, )) } @@ -222,15 +226,21 @@ pub enum WalletStatusMonitorError { #[derive(Clone)] pub struct WalletStatusMonitor { grpc_port: u16, + latest_balance_broadcast: watch::Sender>, } #[async_trait] impl StatusMonitor for WalletStatusMonitor { async fn check_health(&self) -> HealthStatus { - if self.get_balance().await.is_ok() { - HealthStatus::Healthy - } else { - HealthStatus::Unhealthy + match self.get_balance().await { + Ok(b) => { + let _result = self.latest_balance_broadcast.send(Some(b)); + HealthStatus::Healthy + } + Err(e) => { + warn!(target: LOG_TARGET, "Wallet health check failed: {}", e); + HealthStatus::Unhealthy + } } } } @@ -255,8 +265,8 @@ pub struct TransactionInfo { pub is_cancelled: bool, pub excess_sig: String, pub timestamp: u64, - pub message: String, pub payment_id: String, + pub mined_in_block_height: u64, } impl WalletStatusMonitor { @@ -314,8 +324,8 @@ impl WalletStatusMonitor { is_cancelled: tx.is_cancelled, excess_sig: tx.excess_sig.to_hex(), timestamp: tx.timestamp, - message: tx.message, payment_id: tx.payment_id.to_hex(), + mined_in_block_height: tx.mined_in_block_height, }); } Ok(transactions) diff --git a/src-tauri/src/wallet_manager.rs b/src-tauri/src/wallet_manager.rs index 564d8892f..be1d0c4cb 100644 --- a/src-tauri/src/wallet_manager.rs +++ b/src-tauri/src/wallet_manager.rs @@ -30,6 +30,7 @@ use futures_util::future::FusedFuture; use std::path::PathBuf; use std::sync::Arc; use tari_shutdown::ShutdownSignal; +use tokio::sync::watch; use tokio::sync::RwLock; #[derive(thiserror::Error, Debug)] @@ -57,11 +58,14 @@ impl Clone for WalletManager { } impl WalletManager { - pub fn new(node_manager: NodeManager) -> Self { + pub fn new( + node_manager: NodeManager, + wallet_watch_tx: watch::Sender>, + ) -> Self { // TODO: wire up to front end let use_tor = false; - let adapter = WalletAdapter::new(use_tor); + let adapter = WalletAdapter::new(use_tor, wallet_watch_tx); let process_watcher = ProcessWatcher::new(adapter); Self { diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 95fe50045..fe3a9807b 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,5 +1,5 @@ { - "version": "0.8.31", + "version": "0.8.34", "productName": "Tari Universe (Alpha)", "mainBinaryName": "Tari Universe (Alpha)", "identifier": "com.tari.universe.alpha", diff --git a/src/App/AppWrapper.tsx b/src/App/AppWrapper.tsx index 160de9c7e..668e61126 100644 --- a/src/App/AppWrapper.tsx +++ b/src/App/AppWrapper.tsx @@ -1,12 +1,12 @@ import { useEffect } from 'react'; -import { defaultOptions } from 'tauri-plugin-sentry-api'; -import * as Sentry from '@sentry/react'; -import { IGNORE_FETCHING } from '@app/App/sentryIgnore'; +// import { defaultOptions } from 'tauri-plugin-sentry-api'; +// import * as Sentry from '@sentry/react'; +// import { IGNORE_FETCHING } from '@app/App/sentryIgnore'; import { initSystray } from '@app/utils'; import { useDetectMode, useDisableRefresh, useLangaugeResolver, useListenForExternalDependencies } from '@app/hooks'; -import packageInfo from '../../package.json'; +// import packageInfo from '../../package.json'; import { useAppConfigStore } from '../store/useAppConfigStore.ts'; import setupLogger from '../utils/shared-logger.ts'; import App from './App.tsx'; @@ -15,21 +15,21 @@ import { useMiningStore } from '@app/store/useMiningStore.ts'; // FOR ANYTHING THAT NEEDS TO BE INITIALISED -const environment = import.meta.env.MODE; -const sentryOptions = { - ...defaultOptions, - dsn: 'https://edd6b9c1494eb7fda6ee45590b80bcee@o4504839079002112.ingest.us.sentry.io/4507979991285760', - integrations: [Sentry.captureConsoleIntegration({ levels: ['warn', 'error'] }), Sentry.extraErrorDataIntegration()], - release: packageInfo.version, - environment, - // Set tracesSampleRate to 1.0 to capture 100% - // of transactions for tracing. - tracesSampleRate: 1.0, - attachStacktrace: true, - autoSessionTracking: false, - ignoreErrors: [...IGNORE_FETCHING], - enabled: environment !== 'development', -}; +// const environment = import.meta.env.MODE; +// const sentryOptions = { +// ...defaultOptions, +// dsn: 'https://edd6b9c1494eb7fda6ee45590b80bcee@o4504839079002112.ingest.us.sentry.io/4507979991285760', +// integrations: [Sentry.captureConsoleIntegration({ levels: ['warn', 'error'] }), Sentry.extraErrorDataIntegration()], +// release: packageInfo.version, +// environment, +// // Set tracesSampleRate to 1.0 to capture 100% +// // of transactions for tracing. +// tracesSampleRate: 1.0, +// attachStacktrace: true, +// autoSessionTracking: false, +// ignoreErrors: [...IGNORE_FETCHING], +// enabled: environment !== 'development', +// }; setupLogger(); diff --git a/src/components/ToastStack/Toast/Toast.tsx b/src/components/ToastStack/Toast/Toast.tsx index f354eff57..2d6617b8e 100644 --- a/src/components/ToastStack/Toast/Toast.tsx +++ b/src/components/ToastStack/Toast/Toast.tsx @@ -25,13 +25,11 @@ export const Toast = ({ id, index, title, text, timeout = 4500, isHovered = fals const { removeToast } = useToastStore(); const [finished, setFinished] = useState(false); - const handleHide = useCallback( - (id: number | string = 0) => { - setShow(false); - removeToast(id); - }, - [removeToast] - ); + const handleHide = useCallback((id: number | string = 0) => { + setShow(false); + removeToast(id); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); useEffect(() => { setShow(true); diff --git a/src/containers/floating/AutoUpdateDialog/AutoUpdateDialog.tsx b/src/containers/floating/AutoUpdateDialog/AutoUpdateDialog.tsx index fcf0c723b..f00caae53 100644 --- a/src/containers/floating/AutoUpdateDialog/AutoUpdateDialog.tsx +++ b/src/containers/floating/AutoUpdateDialog/AutoUpdateDialog.tsx @@ -61,12 +61,14 @@ export default function AutoUpdateDialog() { return () => { unlistenPromise.then((unlisten) => unlisten()); }; - }, [open, setDialogToShow]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [open]); const handleClose = useCallback(() => { console.info('Update declined'); setDialogToShow(null); - }, [setDialogToShow]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const handleUpdate = useCallback(() => { console.info('Proceed with update'); diff --git a/src/containers/floating/Error/ErrorSnackbar.tsx b/src/containers/floating/Error/ErrorSnackbar.tsx index 6efb3663a..acfa18fc6 100644 --- a/src/containers/floating/Error/ErrorSnackbar.tsx +++ b/src/containers/floating/Error/ErrorSnackbar.tsx @@ -51,14 +51,12 @@ export default function ErrorSnackbar() { const { getFloatingProps } = useInteractions([dismiss, role]); - const handleClose = useCallback( - (e?: MouseEvent) => { - e?.preventDefault(); - e?.stopPropagation(); - setError(undefined); - }, - [setError] - ); + const handleClose = useCallback((e?: MouseEvent) => { + e?.preventDefault(); + e?.stopPropagation(); + setError(undefined); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); useEffect(() => { setShow(Boolean(error && error?.length)); diff --git a/src/containers/floating/Settings/sections/airdrop/ApplyInviteCode.tsx b/src/containers/floating/Settings/sections/airdrop/ApplyInviteCode.tsx index 21eae8652..114079273 100644 --- a/src/containers/floating/Settings/sections/airdrop/ApplyInviteCode.tsx +++ b/src/containers/floating/Settings/sections/airdrop/ApplyInviteCode.tsx @@ -38,7 +38,8 @@ export const ApplyInviteCode = () => { open(refUrl); }); } - }, [backendInMemoryConfig?.airdropTwitterAuthUrl, claimCode, setAllowTelemetry, setAuthUuid]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [backendInMemoryConfig?.airdropTwitterAuthUrl, claimCode]); const handleToken = useCallback(() => { if (authUuid) { @@ -62,7 +63,8 @@ export const ApplyInviteCode = () => { return false; } - }, [authUuid, backendInMemoryConfig?.airdropApiUrl, setAirdropTokens]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [authUuid, backendInMemoryConfig?.airdropApiUrl]); useEffect(() => { if (authUuid && backendInMemoryConfig?.airdropApiUrl) { @@ -85,7 +87,8 @@ export const ApplyInviteCode = () => { setLoading(false); }; } - }, [authUuid, backendInMemoryConfig?.airdropApiUrl, handleToken, setAuthUuid]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [authUuid, backendInMemoryConfig?.airdropApiUrl, handleToken]); return ( diff --git a/src/containers/floating/Settings/sections/p2p/P2PoolStats.tsx b/src/containers/floating/Settings/sections/p2p/P2PoolStats.tsx index 1263be01f..65ab3f762 100644 --- a/src/containers/floating/Settings/sections/p2p/P2PoolStats.tsx +++ b/src/containers/floating/Settings/sections/p2p/P2PoolStats.tsx @@ -38,7 +38,8 @@ const P2PoolStats = () => { return () => { clearInterval(fetchP2pStatsInterval); }; - }, [fetchP2pStats, fetchP2poolConnections]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const displayPeers = useMemo(() => { const sha3Height = sha3Stats?.height; diff --git a/src/containers/floating/ShareRewardModal/ShareRewardModal.tsx b/src/containers/floating/ShareRewardModal/ShareRewardModal.tsx index bd3bbf566..340f191d3 100644 --- a/src/containers/floating/ShareRewardModal/ShareRewardModal.tsx +++ b/src/containers/floating/ShareRewardModal/ShareRewardModal.tsx @@ -52,7 +52,7 @@ export default function ShareRewardModal() { const referralCode = userDetails?.user?.referral_code || ''; const gemsValue = (referralQuestPoints?.pointsForClaimingReferral || GIFT_GEMS).toLocaleString(); - const block = item?.blockHeight || 0; + const block = item?.mined_in_block_height || 0; const reward = item?.amount || 0; const earningsFormatted = useMemo(() => formatNumber(reward, FormatPreset.TXTM_COMPACT).toLowerCase(), [reward]); @@ -71,9 +71,11 @@ export default function ShareRewardModal() { {t('share.title')} - - {t('share.winner-pill')} #{block.toLocaleString()} - + {block ? ( + + {t('share.winner-pill')} #{block.toLocaleString()} + + ) : null} diff --git a/src/containers/main/Airdrop/AirdropGiftTracker/components/ClaimModal/ClaimModal.tsx b/src/containers/main/Airdrop/AirdropGiftTracker/components/ClaimModal/ClaimModal.tsx index 89b636c83..207c8e45e 100644 --- a/src/containers/main/Airdrop/AirdropGiftTracker/components/ClaimModal/ClaimModal.tsx +++ b/src/containers/main/Airdrop/AirdropGiftTracker/components/ClaimModal/ClaimModal.tsx @@ -13,7 +13,6 @@ import { Text, TextWrapper, Title, - XLogo, } from './styles'; import gemImage from './images/gems.png'; import gemLargeImage from './images/gem-large.png'; @@ -21,7 +20,6 @@ import { useCallback, useState } from 'react'; import { GIFT_GEMS, MAX_GEMS, useAirdropStore } from '@app/store/useAirdropStore'; import { Trans, useTranslation } from 'react-i18next'; import { GemImage } from '../Gems/styles'; -import XLogoIcon from './icons/XLogoIcon'; import { useAppConfigStore } from '@app/store/useAppConfigStore'; import GreenModal from '@app/components/GreenModal/GreenModal'; @@ -38,10 +36,15 @@ export default function ClaimModal({ onSubmit, onClose }: ClaimModalProps) { const [claimCode, setClaimCode] = useState(''); - const handleSubmit = useCallback(async () => { - await setAllowTelemetry(true); - return onSubmit(claimCode); - }, [claimCode, onSubmit, setAllowTelemetry]); + const handleSubmit = useCallback( + async (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + await setAllowTelemetry(true); + return onSubmit(claimCode); + }, + [claimCode, onSubmit, setAllowTelemetry] + ); return ( @@ -78,12 +81,7 @@ export default function ClaimModal({ onSubmit, onClose }: ClaimModalProps) { - - {t('claimGems')} - - - - + {t('claimGems')} {!allowTelemetry && ( diff --git a/src/containers/main/Airdrop/AirdropGiftTracker/components/ClaimModal/styles.ts b/src/containers/main/Airdrop/AirdropGiftTracker/components/ClaimModal/styles.ts index ba349c42c..3729ed96f 100644 --- a/src/containers/main/Airdrop/AirdropGiftTracker/components/ClaimModal/styles.ts +++ b/src/containers/main/Airdrop/AirdropGiftTracker/components/ClaimModal/styles.ts @@ -248,25 +248,6 @@ export const InputGems = styled('div')` } `; -export const XLogo = styled('div')` - width: 48px; - height: 48px; - border-radius: 100%; - - display: flex; - align-items: center; - justify-content: center; - - background-color: ${({ theme }) => theme.palette.background.accent}; - color: #000; - - position: absolute; - top: 50%; - right: 16px; - - transform: translateY(-50%); -`; - export const FinePrint = styled('div')` color: #000; text-align: center; diff --git a/src/containers/main/Airdrop/AirdropGiftTracker/sections/LoggedIn/segments/Flare/Flare.tsx b/src/containers/main/Airdrop/AirdropGiftTracker/sections/LoggedIn/segments/Flare/Flare.tsx index a1cf6c6d3..dc64ca234 100644 --- a/src/containers/main/Airdrop/AirdropGiftTracker/sections/LoggedIn/segments/Flare/Flare.tsx +++ b/src/containers/main/Airdrop/AirdropGiftTracker/sections/LoggedIn/segments/Flare/Flare.tsx @@ -22,22 +22,22 @@ const durations = { export default function Flare({ gems, animationType }: Props) { const setFlareAnimationType = useAirdropStore((s) => s.setFlareAnimationType); - const clearFlareAnimationType = useCallback(() => setFlareAnimationType(), [setFlareAnimationType]); useEffect(() => { const duration = durations[animationType] || 0; - const animationTimeout = setTimeout(clearFlareAnimationType, duration); + const animationTimeout = setTimeout(setFlareAnimationType, duration); return () => { clearTimeout(animationTimeout); }; - }, [animationType, clearFlareAnimationType]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [animationType]); return ( setFlareAnimationType()} > {animationType === 'GoalComplete' && } {animationType === 'FriendAccepted' && } diff --git a/src/containers/main/Airdrop/AirdropGiftTracker/sections/LoggedOut/LoggedOut.tsx b/src/containers/main/Airdrop/AirdropGiftTracker/sections/LoggedOut/LoggedOut.tsx index fe72df0c4..0b9f6ee8c 100644 --- a/src/containers/main/Airdrop/AirdropGiftTracker/sections/LoggedOut/LoggedOut.tsx +++ b/src/containers/main/Airdrop/AirdropGiftTracker/sections/LoggedOut/LoggedOut.tsx @@ -1,20 +1,69 @@ import { GIFT_GEMS, useAirdropStore } from '@app/store/useAirdropStore'; import { ClaimButton, GemPill, Image, Title, Wrapper } from './styles'; -import { useState, useEffect } from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import { open } from '@tauri-apps/plugin-shell'; +import { v4 as uuidv4 } from 'uuid'; import ClaimModal from '../../components/ClaimModal/ClaimModal'; import { useTranslation } from 'react-i18next'; import gemImage from '../../images/gem.png'; -import { useAirdropAuth } from '../../hooks/useAirdropAuth'; +import { useMiningStore } from '@app/store/useMiningStore'; export default function LoggedOut() { const [modalIsOpen, setModalIsOpen] = useState(false); const { t } = useTranslation(['airdrop'], { useSuspense: false }); - const { referralQuestPoints } = useAirdropStore(); - const { handleAuth, checkAuth } = useAirdropAuth(); + const restartMining = useMiningStore((s) => s.restartMining); + const { referralQuestPoints, authUuid, setAuthUuid, setAirdropTokens, setUserPoints, backendInMemoryConfig } = + useAirdropStore(); + + const handleAuth = useCallback( + (code?: string) => { + const token = uuidv4(); + if (backendInMemoryConfig?.airdropTwitterAuthUrl) { + setAuthUuid(token); + open( + `${backendInMemoryConfig?.airdropTwitterAuthUrl}?tauri=${token}${code ? `&universeReferral=${code}` : ''}` + ); + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [backendInMemoryConfig?.airdropTwitterAuthUrl] + ); useEffect(() => { - checkAuth(); - }, [checkAuth]); + if (authUuid && backendInMemoryConfig?.airdropApiUrl) { + const interval = setInterval(() => { + if (authUuid) { + fetch(`${backendInMemoryConfig?.airdropApiUrl}/auth/twitter/get-token/${authUuid}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }) + .then((response) => response.json()) + .then((data) => { + if (!data.error) { + clearInterval(interval); + setAirdropTokens(data); + restartMining(); + } + }); + } + }, 1000); + const timeout = setTimeout( + () => { + clearInterval(interval); + setAuthUuid(''); + }, + 1000 * 60 * 5 + ); + + return () => { + clearInterval(interval); + clearTimeout(timeout); + }; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [authUuid, backendInMemoryConfig?.airdropApiUrl]); const gemsValue = (referralQuestPoints?.pointsForClaimingReferral || GIFT_GEMS).toLocaleString(); diff --git a/src/containers/main/Dashboard/MiningView/components/Earnings.tsx b/src/containers/main/Dashboard/MiningView/components/Earnings.tsx index 209465b30..3a86b70ff 100644 --- a/src/containers/main/Dashboard/MiningView/components/Earnings.tsx +++ b/src/containers/main/Dashboard/MiningView/components/Earnings.tsx @@ -52,13 +52,13 @@ export default function Earnings() { ) : null; const replayText = - replayItem?.amount && replayItem.blockHeight ? ( + replayItem?.amount && replayItem.mined_in_block_height ? ( }} /> diff --git a/src/containers/main/ShellOfSecrets/SoSWidget/segments/Timer/Timer.tsx b/src/containers/main/ShellOfSecrets/SoSWidget/segments/Timer/Timer.tsx index 6d371e37a..6c5340792 100644 --- a/src/containers/main/ShellOfSecrets/SoSWidget/segments/Timer/Timer.tsx +++ b/src/containers/main/ShellOfSecrets/SoSWidget/segments/Timer/Timer.tsx @@ -14,7 +14,7 @@ import { useEffect, useState } from 'react'; export default function Timer() { const { t } = useTranslation('sos', { useSuspense: false }); - const { getTimeRemaining } = useShellOfSecretsStore(); + const getTimeRemaining = useShellOfSecretsStore((s) => s.getTimeRemaining); const [reminingTime, setRemainingTime] = useState({ days: 0, hours: 0, totalRemainingMs: 0 }); useEffect(() => { @@ -24,7 +24,8 @@ export default function Timer() { return () => { clearInterval(intervalId); }; - }, [getTimeRemaining]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); return ( diff --git a/src/containers/main/SideBar/Miner/components/CustomPowerLevels/CustomPowerLevelsDialogContainer.tsx b/src/containers/main/SideBar/Miner/components/CustomPowerLevels/CustomPowerLevelsDialogContainer.tsx index aa8ba115b..5e0f23f21 100644 --- a/src/containers/main/SideBar/Miner/components/CustomPowerLevels/CustomPowerLevelsDialogContainer.tsx +++ b/src/containers/main/SideBar/Miner/components/CustomPowerLevels/CustomPowerLevelsDialogContainer.tsx @@ -19,7 +19,8 @@ export const CustomPowerLevelsDialogContainer = () => { if (!maxThreads) { fetchMaxThreads(); } - }, [fetchMaxThreads, maxThreads]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [maxThreads]); return ( { return () => { unlistenPromise.then((unlisten) => unlisten()); }; - }, [setIsOrphanChain]); + }, []); const steps = Array.from({ length: 6 }).map((_, i) => t(`mining-view:orphan-chain-tooltip.step_${i + 1}`)); diff --git a/src/containers/main/SideBar/components/Wallet/History.tsx b/src/containers/main/SideBar/components/Wallet/History.tsx index c5c741733..00894e8bd 100644 --- a/src/containers/main/SideBar/components/Wallet/History.tsx +++ b/src/containers/main/SideBar/components/Wallet/History.tsx @@ -1,11 +1,10 @@ -import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useWalletStore } from '@app/store/useWalletStore'; import { CircularProgress } from '@app/components/elements/CircularProgress'; -import HistoryItem from './HistoryItem'; import { ListLabel } from './HistoryItem.styles'; import { HistoryContainer, HistoryPadding } from './Wallet.styles'; +import HistoryItem from './HistoryItem'; const container = { hidden: { opacity: 0, height: 0 }, @@ -19,13 +18,16 @@ export default function History() { const { t } = useTranslation('sidebar', { useSuspense: false }); const isTransactionLoading = useWalletStore((s) => s.isTransactionLoading); const transactions = useWalletStore((s) => s.transactions); - const txMarkup = useMemo(() => transactions.map((tx) => ), [transactions]); return ( {t('recent-wins')} - {isTransactionLoading && !transactions?.length ? : txMarkup} + {isTransactionLoading && !transactions?.length ? ( + + ) : ( + transactions.map((tx) => ) + )} ); diff --git a/src/containers/main/SideBar/components/Wallet/HistoryItem.tsx b/src/containers/main/SideBar/components/Wallet/HistoryItem.tsx index 38222d11d..55dd575af 100644 --- a/src/containers/main/SideBar/components/Wallet/HistoryItem.tsx +++ b/src/containers/main/SideBar/components/Wallet/HistoryItem.tsx @@ -21,14 +21,14 @@ import { useCallback, useMemo, useState } from 'react'; import { AnimatePresence } from 'framer-motion'; import gemImage from '../../../Airdrop/AirdropGiftTracker/images/gem.png'; import { useShareRewardStore } from '@app/store/useShareRewardStore.ts'; -import { Transaction } from '@app/types/wallet.ts'; import { GIFT_GEMS, useAirdropStore } from '@app/store/useAirdropStore.ts'; import { useAppConfigStore } from '@app/store/useAppConfigStore.ts'; import { ReplaySVG } from '@app/assets/icons/replay'; import { formatNumber, FormatPreset } from '@app/utils/formatters.ts'; +import { TransactionInfo } from '@app/types/app-status.ts'; interface HistoryItemProps { - item: Transaction; + item: TransactionInfo; } const randomGradientColours = [ @@ -71,11 +71,7 @@ export default function HistoryItem({ item }: HistoryItemProps) { handleWinReplay(item); }, [handleWinReplay, item]); - if (!item.blockHeight || item.payment_id?.length > 0) { - return null; - } - - const itemTitle = `${t('block')} #${item.blockHeight}`; + const itemTitle = `${t('block')} #${item.mined_in_block_height}`; const itemTime = new Date(item.timestamp * 1000)?.toLocaleString(systemLang ? undefined : appLanguage, { month: 'short', day: '2-digit', @@ -117,14 +113,19 @@ export default function HistoryItem({ item }: HistoryItemProps) { )} - - {itemTitle} - {itemTime} + {item.mined_in_block_height ? ( + <> + {itemTitle} + {itemTime} + + ) : ( + {itemTime} + )} diff --git a/src/containers/main/SideBar/components/Wallet/Wallet.tsx b/src/containers/main/SideBar/components/Wallet/Wallet.tsx index f1abb2617..3881ea67b 100644 --- a/src/containers/main/SideBar/components/Wallet/Wallet.tsx +++ b/src/containers/main/SideBar/components/Wallet/Wallet.tsx @@ -62,16 +62,16 @@ export default function Wallet() { const toggleBalanceVisibility = () => setShowBalance((prev) => !prev); const displayValue = balance === null ? '-' : showBalance ? formatted : '*****'; - const handleShowClick = useCallback(() => { + const handleShowClick = useCallback(async () => { if (balance && !transactions.length && !isTransactionLoading) { - fetchTx().then(() => setShowHistory((c) => !c)); - return; + await fetchTx(); + } else { + setRecapCount(undefined); } - setRecapCount(undefined); - setShowHistory((c) => !c); - }, [balance, fetchTx, isTransactionLoading, setRecapCount, transactions?.length]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [balance, fetchTx, isTransactionLoading, transactions?.length]); const handleSyncButtonClick = () => { setShowPaperWalletModal(true); diff --git a/src/hooks/airdrop/stateHelpers/useAirdropTokensRefresh.ts b/src/hooks/airdrop/stateHelpers/useAirdropTokensRefresh.ts index 060a67bf2..f7c657e18 100644 --- a/src/hooks/airdrop/stateHelpers/useAirdropTokensRefresh.ts +++ b/src/hooks/airdrop/stateHelpers/useAirdropTokensRefresh.ts @@ -40,7 +40,8 @@ export function useHandleAirdropTokensRefresh() { } await setAirdropTokens(fetchedAirdropTokens); }, - [airdropTokens, setAirdropTokens, syncedAidropWithBackend] + // eslint-disable-next-line react-hooks/exhaustive-deps + [airdropTokens, syncedAidropWithBackend] ); } export function useAirdropTokensRefresh() { diff --git a/src/hooks/airdrop/stateHelpers/useAirdropUserPointsListener.ts b/src/hooks/airdrop/stateHelpers/useAirdropUserPointsListener.ts index 99a9104e5..7810f93e7 100644 --- a/src/hooks/airdrop/stateHelpers/useAirdropUserPointsListener.ts +++ b/src/hooks/airdrop/stateHelpers/useAirdropUserPointsListener.ts @@ -1,12 +1,14 @@ import { useAirdropStore, UserPoints } from '@app/store/useAirdropStore'; +import { deepEqual } from '@app/utils/objectDeepEqual'; import { listen } from '@tauri-apps/api/event'; -import { useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useRef } from 'react'; export const useAirdropUserPointsListener = () => { const setUserPoints = useAirdropStore((state) => state?.setUserPoints); const currentReferralData = useAirdropStore((state) => state?.referralCount); const bonusTiers = useAirdropStore((state) => state.bonusTiers); const setFlareAnimationType = useAirdropStore((state) => state.setFlareAnimationType); + const cachedUserPoints = useRef(); const handleAirdropPoints = useCallback( (pointsPayload: UserPoints) => { @@ -22,12 +24,15 @@ export const useAirdropUserPointsListener = () => { setUserPoints(pointsPayload); } }, - [bonusTiers, currentReferralData?.count, setFlareAnimationType, setUserPoints] + // eslint-disable-next-line react-hooks/exhaustive-deps + [bonusTiers, currentReferralData?.count] ); useEffect(() => { const ul = listen('UserPoints', ({ payload }) => { - if (payload) { + if (!payload) return; + const payloadChanged = !deepEqual(payload as UserPoints, cachedUserPoints.current); + if (payloadChanged) { handleAirdropPoints(payload as UserPoints); } }); diff --git a/src/hooks/airdrop/useWebsocket.ts b/src/hooks/airdrop/useWebsocket.ts index 40151f205..faaa41bfd 100644 --- a/src/hooks/airdrop/useWebsocket.ts +++ b/src/hooks/airdrop/useWebsocket.ts @@ -72,15 +72,8 @@ export const useWebsocket = () => { console.error(e); } }, - [ - connectedSocket, - appId, - height, - applicationsVersions?.tari_universe, - network, - userId, - registerWsConnectionEvent, - ] + // eslint-disable-next-line react-hooks/exhaustive-deps + [connectedSocket, appId, height, applicationsVersions?.tari_universe, network, userId] ); useEffect(() => { diff --git a/src/hooks/app/useListenForExternalDependencies.ts b/src/hooks/app/useListenForExternalDependencies.ts index d5586b86e..b110503af 100644 --- a/src/hooks/app/useListenForExternalDependencies.ts +++ b/src/hooks/app/useListenForExternalDependencies.ts @@ -17,5 +17,6 @@ export function useListenForExternalDependencies() { return () => { unlistenPromise.then((unlisten) => unlisten()); }; - }, [loadExternalDependencies, setShowExternalDependenciesDialog]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); } diff --git a/src/hooks/app/useSetUp.ts b/src/hooks/app/useSetUp.ts index cbda61289..badbf25ab 100644 --- a/src/hooks/app/useSetUp.ts +++ b/src/hooks/app/useSetUp.ts @@ -42,10 +42,12 @@ export function useSetUp() { localStorage.setItem('airdrop-store', airdropStorage); } }, []); + const handlePostSetup = useCallback(async () => { await fetchApplicationsVersionsWithRetry(); await setSettingUpFinished(); - }, [fetchApplicationsVersionsWithRetry, setSettingUpFinished]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); useEffect(() => { if (adminShow === 'setup') return; @@ -75,5 +77,6 @@ export function useSetUp() { return () => { unlistenPromise.then((unlisten) => unlisten()); }; - }, [clearStorage, handlePostSetup, setCriticalError, setSetupDetails, adminShow, syncedAidropWithBackend]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [clearStorage, handlePostSetup, adminShow, syncedAidropWithBackend]); } diff --git a/src/hooks/app/useSystemTray.ts b/src/hooks/app/useSystemTray.ts index 038ae8b35..867cc903e 100644 --- a/src/hooks/app/useSystemTray.ts +++ b/src/hooks/app/useSystemTray.ts @@ -9,22 +9,22 @@ import { } from '@app/utils'; import { listen } from '@tauri-apps/api/event'; import { getCurrentWindow } from '@tauri-apps/api/window'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useRef } from 'react'; import { formatHashrate, formatNumber, FormatPreset } from '@app/utils'; import { MenuItem } from '@tauri-apps/api/menu/menuItem'; const currentWindow = getCurrentWindow(); -export function useUpdateSystemTray() { - const [metrics, setMetrics] = useState(); +const SysTrayCopy = { + [CPU_HASH_ITEM_ID]: (cpu: string) => `CPU Hashrate: ${cpu}`, + [GPU_HASH_ITEM_ID]: (gpu: string) => `GPU Hashrate: ${gpu}`, + [EARNINGS_ITEM_ID]: (earnings: string) => `Est earning: ${earnings} tXTM/day`, +}; - const totalEarningsFormatted = useMemo(() => { - const cpu_est = metrics?.cpu?.mining?.estimated_earnings || 0; - const gpu_est = metrics?.gpu?.mining?.estimated_earnings || 0; - const total = cpu_est + gpu_est; - return total > 0 ? formatNumber(total, FormatPreset.TXTM_COMPACT) : '0'; - }, [metrics]); +export function useUpdateSystemTray() { + const cachedMetrics = useRef(); + const minimizedRef = useRef(); const updateMenuItemEnabled = useCallback(async (itemId: string, enabled: boolean) => { const item = await menu.get(itemId); @@ -37,45 +37,49 @@ export function useUpdateSystemTray() { } } }, []); - const updateMenuItem = useCallback(async ({ itemId, itemText }: { itemId: string; itemText?: string }) => { + + const updateMenuItem = useCallback(async (itemId: string, itemText: string) => { const item = await menu.get(itemId); if (item && itemText) { await item.setText(itemText); } }, []); - const items = useMemo(() => { - const { cpu, gpu } = metrics || {}; - const cpu_h = cpu?.mining?.hash_rate || 0; - const gpu_h = gpu?.mining?.hash_rate || 0; - - const cpuHashItemText = `CPU Hashrate: ${cpu_h ? `${formatHashrate(cpu_h)}` : '-'}`; - const gpuHashItemText = `GPU Hashrate: ${gpu_h ? `${formatHashrate(gpu_h)}` : '-'}`; - const estEarningsItemText = `Est earning: ${totalEarningsFormatted !== '0' ? totalEarningsFormatted : '-'} tXTM/day`; + useEffect(() => { + const handleUpdateMenu = () => { + const { cpu, gpu } = cachedMetrics.current || {}; - return [ - { itemId: CPU_HASH_ITEM_ID, itemText: cpuHashItemText }, - { itemId: GPU_HASH_ITEM_ID, itemText: gpuHashItemText }, - { itemId: EARNINGS_ITEM_ID, itemText: estEarningsItemText }, - ]; - }, [metrics, totalEarningsFormatted]); + // --- Update CPU + const cpuHashItemText = cpu?.mining?.hash_rate ? `${formatHashrate(cpu?.mining?.hash_rate)}` : '-'; + updateMenuItem(CPU_HASH_ITEM_ID, SysTrayCopy[CPU_HASH_ITEM_ID](cpuHashItemText)); + // --- Update GPU + const gpuHashItemText = gpu?.mining?.hash_rate ? `${formatHashrate(gpu?.mining?.hash_rate)}` : '-'; + updateMenuItem(GPU_HASH_ITEM_ID, SysTrayCopy[GPU_HASH_ITEM_ID](gpuHashItemText)); + // --- Update Total + const cpu_est = cpu?.mining?.estimated_earnings || 0; + const gpu_est = gpu?.mining?.estimated_earnings || 0; + const total = cpu_est + gpu_est; + const totalFormatted = total > 0 ? formatNumber(total, FormatPreset.TXTM_COMPACT) : '-'; + updateMenuItem(EARNINGS_ITEM_ID, SysTrayCopy[EARNINGS_ITEM_ID](totalFormatted)); + }; + handleUpdateMenu(); + const interval = setInterval(handleUpdateMenu, 1000 * 10); // 10s - useEffect(() => { - items.forEach(async (item) => { - await updateMenuItem({ ...item }); - }); - }, [items, updateMenuItem]); + return () => clearInterval(interval); + }, [updateMenuItem]); useEffect(() => { const ul = listen('miner_metrics', async ({ payload }) => { - const minimized = await currentWindow.isMinimized(); - if (payload) { - setMetrics(payload as MinerMetrics); + cachedMetrics.current = { ...payload } as MinerMetrics; } - await updateMenuItemEnabled(UNMINIMIZE_ITEM_ID, minimized); - await updateMenuItemEnabled(MINIMIZE_ITEM_ID, !minimized); + const minimized = await currentWindow.isMinimized(); + if (minimizedRef.current !== minimized) { + minimizedRef.current = minimized; + await updateMenuItemEnabled(UNMINIMIZE_ITEM_ID, minimized); + await updateMenuItemEnabled(MINIMIZE_ITEM_ID, !minimized); + } }); return () => { ul.then((unlisten) => unlisten()); diff --git a/src/hooks/helpers/useDetectMode.ts b/src/hooks/helpers/useDetectMode.ts index 826eaeaa3..8239363af 100644 --- a/src/hooks/helpers/useDetectMode.ts +++ b/src/hooks/helpers/useDetectMode.ts @@ -20,5 +20,6 @@ export function useDetectMode() { return () => { listener.then((unlisten) => unlisten()); }; - }, [setTheme, configTheme]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [configTheme]); } diff --git a/src/hooks/mining/useEarningsRecap.ts b/src/hooks/mining/useEarningsRecap.ts index 96aae9d13..a4b5d3652 100644 --- a/src/hooks/mining/useEarningsRecap.ts +++ b/src/hooks/mining/useEarningsRecap.ts @@ -3,6 +3,7 @@ import { useCallback, useEffect } from 'react'; import { listen } from '@tauri-apps/api/event'; import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'; import { useWalletStore } from '@app/store/useWalletStore.ts'; +import { debounce } from '@app/utils/debounce'; const appWindow = getCurrentWebviewWindow(); export default function useEarningsRecap() { @@ -20,17 +21,23 @@ export default function useEarningsRecap() { handleWinRecap({ count, totalEarnings }); } } - }, [handleWinRecap, recapIds, transactions]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [recapIds, transactions]); useEffect(() => { + // Debounced function to check if the window is minimized + const debouncedIsMinimized = debounce(async () => { + const minimized = await appWindow?.isMinimized(); + const documentIsVisible = document?.visibilityState === 'visible' || false; + if (documentIsVisible && !minimized) { + getMissedEarnings(); + } + }, 1000); // 1 second debounce + const listener = listen( 'tauri://focus', - async () => { - const minimized = await appWindow?.isMinimized(); - const documentIsVisible = document?.visibilityState === 'visible' || false; - if (documentIsVisible && !minimized) { - getMissedEarnings(); - } + () => { + debouncedIsMinimized(); }, { target: { kind: 'WebviewWindow', label: 'main' } } ); diff --git a/src/hooks/mining/useMiningMetricsUpdater.ts b/src/hooks/mining/useMiningMetricsUpdater.ts index 128f0193e..d60cd6693 100644 --- a/src/hooks/mining/useMiningMetricsUpdater.ts +++ b/src/hooks/mining/useMiningMetricsUpdater.ts @@ -48,14 +48,7 @@ export default function useMiningMetricsUpdater() { setMiningMetrics(metrics); } }, - [ - baseNodeConnected, - currentBlockHeight, - displayBlockHeight, - fetchTx, - handleNewBlock, - setDisplayBlockHeight, - setMiningMetrics, - ] + // eslint-disable-next-line react-hooks/exhaustive-deps + [baseNodeConnected, currentBlockHeight, displayBlockHeight, fetchTx] ); } diff --git a/src/hooks/mining/useMiningStatesSync.ts b/src/hooks/mining/useMiningStatesSync.ts index 1250fcce3..3bf9b8ef3 100644 --- a/src/hooks/mining/useMiningStatesSync.ts +++ b/src/hooks/mining/useMiningStatesSync.ts @@ -1,6 +1,6 @@ import { MinerMetrics } from '@app/types/app-status'; import { listen } from '@tauri-apps/api/event'; -import { useCallback, useEffect } from 'react'; +import { useEffect, useRef } from 'react'; import { useWalletStore } from '@app/store/useWalletStore.ts'; import { useAppStateStore } from '@app/store/appStateStore'; @@ -9,43 +9,42 @@ import { useBlockInfo } from './useBlockInfo.ts'; import { useUiMiningStateMachine } from './useMiningUiStateMachine.ts'; import useMiningMetricsUpdater from './useMiningMetricsUpdater.ts'; import useEarningsRecap from './useEarningsRecap.ts'; +import { deepEqual } from '@app/utils/objectDeepEqual.ts'; export function useMiningStatesSync() { const handleMiningMetrics = useMiningMetricsUpdater(); const fetchWalletDetails = useWalletStore((s) => s.fetchWalletDetails); const setupProgress = useAppStateStore((s) => s.setupProgress); const isSettingUp = useAppStateStore((s) => s.isSettingUp); + const prevPayload = useRef(); useBlockInfo(); useUiMiningStateMachine(); useEarningsRecap(); - const callIntervalItems = useCallback(async () => { - if (setupProgress >= 0.75) { - await fetchWalletDetails(); - } - }, [fetchWalletDetails, setupProgress]); - // intervalItems useEffect(() => { - const fetchInterval = setInterval(async () => { - await callIntervalItems(); - }, 1000); - + if (setupProgress < 0.75) return; + const fetchInterval = setInterval(fetchWalletDetails, 1000); return () => { clearInterval(fetchInterval); }; - }, [callIntervalItems]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [setupProgress]); useEffect(() => { if (isSettingUp) return; const ul = listen('miner_metrics', async ({ payload }) => { - if (payload) { + if (!payload) return; + const payloadChanged = !deepEqual(payload as MinerMetrics, prevPayload.current); + if (payloadChanged) { + prevPayload.current = payload as MinerMetrics; await handleMiningMetrics(payload as MinerMetrics); } }); return () => { ul.then((unlisten) => unlisten()); }; - }, [handleMiningMetrics, isSettingUp]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isSettingUp]); } diff --git a/src/hooks/mining/useTransactions.ts b/src/hooks/mining/useTransactions.ts index cddf30a08..c92c89a56 100644 --- a/src/hooks/mining/useTransactions.ts +++ b/src/hooks/mining/useTransactions.ts @@ -3,27 +3,13 @@ import { useCallback } from 'react'; import { invoke } from '@tauri-apps/api/core'; import { useAppStateStore } from '@app/store/appStateStore.ts'; import { useWalletStore } from '@app/store/useWalletStore.ts'; -import { Transaction } from '@app/types/wallet.ts'; export default function useFetchTx() { - const transactions = useWalletStore((s) => s.transactions); const isTransactionLoading = useWalletStore((s) => s.isTransactionLoading); const setTransactionsLoading = useWalletStore((s) => s.setTransactionsLoading); const setupProgress = useAppStateStore((s) => s.setupProgress); const setTransactions = useWalletStore((s) => s.setTransactions); - const setItems = useCallback( - async (newTx: Transaction[]) => { - const latestTx = newTx[0]; - const latestId = latestTx?.tx_id; - const hasNewItems = !transactions?.find((tx) => tx.tx_id === latestId); - if (hasNewItems) { - setTransactions(newTx); - } - }, - [setTransactions, transactions] - ); - return useCallback(async () => { if (isTransactionLoading || setupProgress < 0.75) return; setTransactionsLoading(true); @@ -31,18 +17,8 @@ export default function useFetchTx() { try { const txs = await invoke('get_transaction_history'); const sortedTransactions = txs.sort((a, b) => b.timestamp - a.timestamp); - const mapped = sortedTransactions?.map((tx) => { - const blockHeight = tx.message.split(': ')[1]; - - if (blockHeight) { - return { ...tx, blockHeight }; - } - - return tx; - }) as Transaction[]; - - if (mapped?.length) { - await setItems(mapped); + if (sortedTransactions?.length) { + setTransactions(sortedTransactions); } setTransactionsLoading(false); } catch (error) { @@ -54,5 +30,6 @@ export default function useFetchTx() { } finally { setTransactionsLoading(false); } - }, [isTransactionLoading, setItems, setTransactionsLoading, setupProgress]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isTransactionLoading, setupProgress]); } diff --git a/src/hooks/useListenForCriticalProblem.tsx b/src/hooks/useListenForCriticalProblem.tsx index a3b090360..ae54c0d51 100644 --- a/src/hooks/useListenForCriticalProblem.tsx +++ b/src/hooks/useListenForCriticalProblem.tsx @@ -13,7 +13,8 @@ const useListenForCriticalProblem = () => { return () => { unlistenPromise.then((unlisten) => unlisten()); }; - }, [setCriticalProblem]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); }; export default useListenForCriticalProblem; diff --git a/src/store/useBlockchainVisualisationStore.ts b/src/store/useBlockchainVisualisationStore.ts index c682c4cab..ce70b69db 100644 --- a/src/store/useBlockchainVisualisationStore.ts +++ b/src/store/useBlockchainVisualisationStore.ts @@ -1,4 +1,3 @@ -import { Transaction } from '@app/types/wallet'; import { create } from './create'; import { useMiningStore } from './useMiningStore.ts'; @@ -21,7 +20,7 @@ interface State { recapData?: Recap; recapCount?: number; recapIds: TransactionInfo['tx_id'][]; - replayItem?: Transaction; + replayItem?: TransactionInfo; } interface WinAnimation { @@ -32,7 +31,7 @@ interface WinAnimation { interface Actions { handleWin: ({ latestTx, canAnimate, isRecap }: WinAnimation) => Promise; handleWinRecap: (recapData: Recap) => void; - handleWinReplay: (txItem: Transaction) => void; + handleWinReplay: (txItem: TransactionInfo) => void; handleFail: (blockHeight: number, canAnimate?: boolean) => Promise; handleNewBlock: (newBlockHeight: number, isMining?: boolean) => Promise; setDisplayBlockHeight: (displayBlockHeight: number) => void; @@ -100,7 +99,7 @@ export const useBlockchainVisualisationStore = create { - const blockHeight = Number(latestTx?.message?.split(': ')[1]); + const blockHeight = Number(latestTx?.mined_in_block_height); const earnings = latestTx.amount; console.info(`Block #${blockHeight} mined! Earnings: ${earnings}`); diff --git a/src/store/useShareRewardStore.ts b/src/store/useShareRewardStore.ts index ffb15ffd6..4b5658fa3 100644 --- a/src/store/useShareRewardStore.ts +++ b/src/store/useShareRewardStore.ts @@ -1,14 +1,14 @@ +import { TransactionInfo } from '@app/types/app-status.ts'; import { create } from './create.ts'; -import { Transaction } from '@app/types/wallet.ts'; interface State { showModal: boolean; - item: Transaction | null; + item: TransactionInfo | null; } interface Actions { setShowModal: (showModal: boolean) => void; - setItemData: (item: Transaction | null) => void; + setItemData: (item: TransactionInfo | null) => void; } const initialState: State = { diff --git a/src/store/useWalletStore.ts b/src/store/useWalletStore.ts index c9e83eda8..c62b9005a 100644 --- a/src/store/useWalletStore.ts +++ b/src/store/useWalletStore.ts @@ -1,15 +1,14 @@ import { ALREADY_FETCHING } from '@app/App/sentryIgnore'; import { create } from './create'; -import { WalletBalance } from '../types/app-status.ts'; +import { TransactionInfo, WalletBalance } from '../types/app-status.ts'; import { invoke } from '@tauri-apps/api/core'; -import { Transaction } from '@app/types/wallet.ts'; interface State extends WalletBalance { tari_address_base58: string; tari_address_emoji: string; tari_address?: string; balance: number | null; - transactions: Transaction[]; + transactions: TransactionInfo[]; isTransactionLoading: boolean; is_wallet_importing: boolean; } @@ -17,7 +16,7 @@ interface State extends WalletBalance { interface Actions { fetchWalletDetails: () => Promise; setTransactionsLoading: (isTransactionLoading: boolean) => void; - setTransactions: (transactions?: Transaction[]) => void; + setTransactions: (transactions?: TransactionInfo[]) => void; importSeedWords: (seedWords: string[]) => Promise; } diff --git a/src/types/app-status.ts b/src/types/app-status.ts index 84737ed6d..79de36529 100644 --- a/src/types/app-status.ts +++ b/src/types/app-status.ts @@ -113,6 +113,7 @@ export interface TransactionInfo { timestamp: number; message: string; payment_id: string; + mined_in_block_height?: number; } export interface P2poolStatsResult { diff --git a/src/types/wallet.ts b/src/types/wallet.ts deleted file mode 100644 index 278266184..000000000 --- a/src/types/wallet.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { TransactionInfo } from '@app/types/app-status.ts'; - -export interface Transaction extends TransactionInfo { - blockHeight?: number; -} diff --git a/src/utils/debounce.ts b/src/utils/debounce.ts new file mode 100644 index 000000000..2d745f534 --- /dev/null +++ b/src/utils/debounce.ts @@ -0,0 +1,8 @@ +// Custom debounce function +export function debounce(func: (...args: unknown[]) => void, wait: number) { + let timeout: NodeJS.Timeout; + return (...args: unknown[]) => { + clearTimeout(timeout); + timeout = setTimeout(() => func(...args), wait); + }; +} diff --git a/src/utils/objectDeepEqual.ts b/src/utils/objectDeepEqual.ts new file mode 100644 index 000000000..bd771a663 --- /dev/null +++ b/src/utils/objectDeepEqual.ts @@ -0,0 +1,19 @@ +export function deepEqual(obj1: unknown, obj2: unknown) { + if (obj1 === obj2) return true; + if (typeof obj1 !== 'object' || typeof obj2 !== 'object' || obj1 == null || obj2 == null) { + return false; + } + + const keys1 = Object.keys(obj1); + const keys2 = Object.keys(obj2); + + if (keys1.length !== keys2.length) return false; + + for (const key of keys1) { + if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) { + return false; + } + } + + return true; +}