From b29b23a5ff03dc8a1e265e0ccc65f6e18b9dbd99 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:38:51 +0100 Subject: [PATCH 1/8] fix: connection status not updated --- src/app.rs | 212 +++++++++++----------- src/context.rs | 10 +- src/context/connection_status.rs | 294 +++++++++++++++++++++++++++++++ src/ui/components/top_panel.rs | 21 +-- src/ui/network_chooser_screen.rs | 180 +++++++++++++------ 5 files changed, 544 insertions(+), 173 deletions(-) create mode 100644 src/context/connection_status.rs diff --git a/src/app.rs b/src/app.rs index cecc1a608..6283565e5 100644 --- a/src/app.rs +++ b/src/app.rs @@ -789,14 +789,14 @@ impl App for AppState { // Apply Dash theme with user preference crate::ui::theme::apply_theme(ctx, self.theme_preference); - if let Ok(event) = self.current_app_context().rx_zmq_status.try_recv() - && let Ok(mut status) = self.current_app_context().zmq_connection_status.lock() - { - *status = event; - } + let active_context = self.current_app_context().clone(); // Poll the receiver for any new task results while let Ok(task_result) = self.task_result_receiver.try_recv() { + active_context + .connection_status() + .handle_task_result(&task_result, active_context.network); + // Handle the result on the main thread match task_result { TaskResult::Success(message) => { @@ -984,112 +984,120 @@ impl App for AppState { } // Show welcome screen if onboarding not completed - let action = if self.show_welcome_screen { - if let Some(welcome_screen) = &mut self.welcome_screen { - welcome_screen.ui(ctx) - } else { - AppAction::None - } + let mut actions = Vec::new(); + if self.show_welcome_screen + && let Some(welcome_screen) = &mut self.welcome_screen + { + actions.push(welcome_screen.ui(ctx)); } else { - self.visible_screen_mut().ui(ctx) + actions.push(self.visible_screen_mut().ui(ctx)); }; - match action { - AppAction::AddScreen(screen) => self.screen_stack.push(screen), - AppAction::None => {} - AppAction::Refresh => self.visible_screen_mut().refresh(), - AppAction::PopScreen => { - if !self.screen_stack.is_empty() { - self.screen_stack.pop(); + // Schedule connection status refresh + actions.push( + active_context + .connection_status() + .trigger_refresh(active_context.as_ref()), + ); + + for action in actions { + match action { + AppAction::None => {} + AppAction::AddScreen(screen) => self.screen_stack.push(screen), + AppAction::Refresh => self.visible_screen_mut().refresh(), + AppAction::PopScreen => { + if !self.screen_stack.is_empty() { + self.screen_stack.pop(); + } } - } - AppAction::PopScreenAndRefresh => { - if !self.screen_stack.is_empty() { - self.screen_stack.pop(); + AppAction::PopScreenAndRefresh => { + if !self.screen_stack.is_empty() { + self.screen_stack.pop(); + } + if let Some(screen) = self.screen_stack.last_mut() { + screen.refresh(); + } else { + self.active_root_screen_mut().refresh_on_arrival(); + } } - if let Some(screen) = self.screen_stack.last_mut() { - screen.refresh(); - } else { + AppAction::GoToMainScreen => { + self.screen_stack = vec![]; self.active_root_screen_mut().refresh_on_arrival(); } - } - AppAction::GoToMainScreen => { - self.screen_stack = vec![]; - self.active_root_screen_mut().refresh_on_arrival(); - } - AppAction::BackendTask(task) => { - self.handle_backend_task(task); - } - AppAction::BackendTasks(tasks, mode) => { - self.handle_backend_tasks(tasks, mode); - } - AppAction::SetMainScreen(root_screen_type) => { - self.selected_main_screen = root_screen_type; - self.active_root_screen_mut().refresh_on_arrival(); - self.current_app_context() - .update_settings(root_screen_type) - .ok(); - } - AppAction::SetMainScreenThenGoToMainScreen(root_screen_type) => { - self.selected_main_screen = root_screen_type; - self.active_root_screen_mut().refresh_on_arrival(); - self.current_app_context() - .update_settings(root_screen_type) - .ok(); - self.screen_stack = vec![]; - } - AppAction::SetMainScreenThenPopScreen(root_screen_type) => { - self.selected_main_screen = root_screen_type; - self.active_root_screen_mut().refresh_on_arrival(); - self.current_app_context() - .update_settings(root_screen_type) - .ok(); - if !self.screen_stack.is_empty() { - self.screen_stack.pop(); + AppAction::BackendTask(task) => { + self.handle_backend_task(task); } - } - AppAction::SwitchNetwork(network) => { - self.change_network(network); - self.current_app_context() - .update_settings(RootScreenType::RootScreenNetworkChooser) - .ok(); - } - AppAction::PopThenAddScreenToMainScreen(root_screen_type, screen) => { - self.screen_stack = vec![screen]; - self.selected_main_screen = root_screen_type; - self.active_root_screen_mut().refresh_on_arrival(); - self.current_app_context() - .update_settings(root_screen_type) - .ok(); - } - AppAction::Custom(_) => {} - AppAction::OnboardingComplete { - main_screen, - add_screen, - } => { - self.show_welcome_screen = false; - self.welcome_screen = None; - self.selected_main_screen = main_screen; - self.active_root_screen_mut().refresh_on_arrival(); - self.current_app_context().update_settings(main_screen).ok(); - // If there's an additional screen to push, create and push it - if let Some(screen_type) = add_screen { - let screen = screen_type.create_screen(self.current_app_context()); - self.screen_stack.push(screen); + AppAction::BackendTasks(tasks, mode) => { + self.handle_backend_tasks(tasks, mode); } - // Start SPV sync after onboarding completes (if auto-start is enabled and developer mode is on) - // TODO: SPV auto-start is gated behind developer mode while SPV is in development. - // Remove the is_developer_mode() check once SPV is production-ready. - let current_context = self.current_app_context(); - let auto_start_spv = current_context.db.get_auto_start_spv().unwrap_or(false); - if auto_start_spv - && current_context.is_developer_mode() - && current_context.core_backend_mode() == crate::spv::CoreBackendMode::Spv - { - if let Err(e) = current_context.start_spv() { - tracing::warn!("Failed to start SPV sync after onboarding: {}", e); - } else { - tracing::info!("SPV sync started after onboarding"); + AppAction::SetMainScreen(root_screen_type) => { + self.selected_main_screen = root_screen_type; + self.active_root_screen_mut().refresh_on_arrival(); + self.current_app_context() + .update_settings(root_screen_type) + .ok(); + } + AppAction::SetMainScreenThenGoToMainScreen(root_screen_type) => { + self.selected_main_screen = root_screen_type; + self.active_root_screen_mut().refresh_on_arrival(); + self.current_app_context() + .update_settings(root_screen_type) + .ok(); + self.screen_stack = vec![]; + } + AppAction::SetMainScreenThenPopScreen(root_screen_type) => { + self.selected_main_screen = root_screen_type; + self.active_root_screen_mut().refresh_on_arrival(); + self.current_app_context() + .update_settings(root_screen_type) + .ok(); + if !self.screen_stack.is_empty() { + self.screen_stack.pop(); + } + } + AppAction::SwitchNetwork(network) => { + self.change_network(network); + self.current_app_context() + .update_settings(RootScreenType::RootScreenNetworkChooser) + .ok(); + } + AppAction::PopThenAddScreenToMainScreen(root_screen_type, screen) => { + self.screen_stack = vec![screen]; + self.selected_main_screen = root_screen_type; + self.active_root_screen_mut().refresh_on_arrival(); + self.current_app_context() + .update_settings(root_screen_type) + .ok(); + } + AppAction::Custom(_) => {} + AppAction::OnboardingComplete { + main_screen, + add_screen, + } => { + self.show_welcome_screen = false; + self.welcome_screen = None; + self.selected_main_screen = main_screen; + self.active_root_screen_mut().refresh_on_arrival(); + self.current_app_context().update_settings(main_screen).ok(); + // If there's an additional screen to push, create and push it + if let Some(screen_type) = add_screen { + let screen = screen_type.create_screen(self.current_app_context()); + self.screen_stack.push(screen); + } + // Start SPV sync after onboarding completes (if auto-start is enabled and developer mode is on) + // TODO: SPV auto-start is gated behind developer mode while SPV is in development. + // Remove the is_developer_mode() check once SPV is production-ready. + let current_context = self.current_app_context(); + let auto_start_spv = current_context.db.get_auto_start_spv().unwrap_or(false); + if auto_start_spv + && current_context.is_developer_mode() + && current_context.core_backend_mode() == crate::spv::CoreBackendMode::Spv + { + if let Err(e) = current_context.start_spv() { + tracing::warn!("Failed to start SPV sync after onboarding: {}", e); + } else { + tracing::info!("SPV sync started after onboarding"); + } } } } diff --git a/src/context.rs b/src/context.rs index 4d7be12ff..b0e1ab2b0 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,6 +1,7 @@ use crate::app_dir::core_cookie_path; use crate::backend_task::contested_names::ScheduledDPNSVote; use crate::components::core_zmq_listener::ZMQConnectionEvent; +pub mod connection_status; use crate::config::{Config, NetworkConfig}; use crate::context_provider::Provider as RpcProvider; use crate::context_provider_spv::SpvProvider; @@ -18,6 +19,7 @@ use crate::model::wallet::{ }; use crate::sdk_wrapper::initialize_sdk; use crate::spv::{CoreBackendMode, SpvManager}; +use connection_status::ConnectionStatus; use crate::ui::RootScreenType; use crate::ui::tokens::tokens_screen::{IdentityTokenBalance, IdentityTokenIdentifier}; use crate::utils::tasks::TaskManager; @@ -75,7 +77,6 @@ pub struct AppContext { pub(crate) config: Arc>, pub(crate) rx_zmq_status: Receiver, pub(crate) sx_zmq_status: Sender, - pub(crate) zmq_connection_status: Mutex, pub(crate) dpns_contract: Arc, pub(crate) withdraws_contract: Arc, pub(crate) dashpay_contract: Arc, @@ -100,6 +101,7 @@ pub struct AppContext { pub(crate) subtasks: Arc, pub(crate) spv_manager: Arc, core_backend_mode: AtomicU8, + pub(crate) connection_status: ConnectionStatus, /// Pending wallet selection - set after creating/importing a wallet /// so the wallet screen can auto-select the new wallet pub(crate) pending_wallet_selection: Mutex>, @@ -270,12 +272,12 @@ impl AppContext { single_key_wallets: RwLock::new(single_key_wallets), password_info, transactions_waiting_for_finality: Mutex::new(BTreeMap::new()), - zmq_connection_status: Mutex::new(ZMQConnectionEvent::Disconnected), animate, cached_settings: RwLock::new(None), subtasks, spv_manager, core_backend_mode: AtomicU8::new(saved_core_backend_mode), + connection_status: ConnectionStatus::new(), pending_wallet_selection: Mutex::new(None), selected_wallet_hash: Mutex::new(selected_wallet_hash), selected_single_key_hash: Mutex::new(selected_single_key_hash), @@ -349,6 +351,10 @@ impl AppContext { self.core_backend_mode.load(Ordering::Relaxed).into() } + pub fn connection_status(&self) -> &ConnectionStatus { + &self.connection_status + } + pub fn set_core_backend_mode(self: &Arc, mode: CoreBackendMode) { self.core_backend_mode .store(mode.as_u8(), Ordering::Relaxed); diff --git a/src/context/connection_status.rs b/src/context/connection_status.rs new file mode 100644 index 000000000..0172409c0 --- /dev/null +++ b/src/context/connection_status.rs @@ -0,0 +1,294 @@ +use crate::app::AppAction; +use crate::app::TaskResult; +use crate::backend_task::BackendTask; +use crate::backend_task::BackendTaskSuccessResult; +use crate::backend_task::core::{CoreItem, CoreTask}; +use crate::components::core_zmq_listener::ZMQConnectionEvent; +use crate::spv::{CoreBackendMode, SpvStatus}; +use dash_sdk::dpp::dashcore::{ChainLock, Network}; +use std::sync::Mutex; +use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; +use std::time::{Duration, Instant}; + +const REFRESH_CONNECTED: Duration = Duration::from_secs(10); +const REFRESH_DISCONNECTED: Duration = Duration::from_secs(2); +#[derive(Debug)] +pub struct ConnectionStatus { + rpc_online: AtomicBool, + zmq_status: Mutex, + spv_status: AtomicU8, + backend_mode: AtomicU8, + disable_zmq: AtomicBool, + overall_connected: AtomicBool, + last_update: Mutex, +} + +impl ConnectionStatus { + pub fn new() -> Self { + Self { + rpc_online: AtomicBool::new(false), + zmq_status: Mutex::new(ZMQConnectionEvent::Disconnected), + spv_status: AtomicU8::new(SpvStatus::Idle as u8), + backend_mode: AtomicU8::new(CoreBackendMode::Rpc.as_u8()), + disable_zmq: AtomicBool::new(false), + overall_connected: AtomicBool::new(false), + last_update: Mutex::new(Instant::now()), + } + } + + pub fn rpc_online(&self) -> bool { + self.rpc_online.load(Ordering::Relaxed) + } + + pub fn set_rpc_online(&self, online: bool) { + self.rpc_online.store(online, Ordering::Relaxed); + } + + pub fn zmq_connected(&self) -> bool { + self.zmq_status + .lock() + .map(|status| matches!(*status, ZMQConnectionEvent::Connected)) + .unwrap_or(false) + } + + pub fn set_zmq_status(&self, event: ZMQConnectionEvent) { + if let Ok(mut status) = self.zmq_status.lock() { + *status = event; + } + } + + pub fn spv_status(&self) -> SpvStatus { + match self.spv_status.load(Ordering::Relaxed) { + 1 => SpvStatus::Starting, + 2 => SpvStatus::Syncing, + 3 => SpvStatus::Running, + 4 => SpvStatus::Stopping, + 5 => SpvStatus::Stopped, + 6 => SpvStatus::Error, + _ => SpvStatus::Idle, + } + } + + pub fn set_spv_status(&self, status: SpvStatus) { + self.spv_status.store(status as u8, Ordering::Relaxed); + } + + pub fn backend_mode(&self) -> CoreBackendMode { + self.backend_mode.load(Ordering::Relaxed).into() + } + + pub fn set_backend_mode(&self, mode: CoreBackendMode) { + self.backend_mode.store(mode.as_u8(), Ordering::Relaxed); + } + + pub fn disable_zmq(&self) -> bool { + self.disable_zmq.load(Ordering::Relaxed) + } + + pub fn set_disable_zmq(&self, disable: bool) { + self.disable_zmq.store(disable, Ordering::Relaxed); + } + + pub fn spv_connected(status: SpvStatus) -> bool { + status.is_active() || status == SpvStatus::Running + } + + pub fn rpc_connected(&self) -> bool { + self.rpc_online() + } + + pub fn zmq_required(&self) -> bool { + !self.disable_zmq() + } + + pub fn rpc_zmq_healthy(&self) -> bool { + self.rpc_online() && (self.disable_zmq() || self.zmq_connected()) + } + + pub fn overall_connected(&self) -> bool { + self.overall_connected.load(Ordering::Relaxed) + } + + pub fn refresh_overall(&self) { + let backend_mode = self.backend_mode(); + let disable_zmq = self.disable_zmq(); + let spv_status = self.spv_status(); + let connected = match backend_mode { + CoreBackendMode::Rpc => self.rpc_online() && (disable_zmq || self.zmq_connected()), + CoreBackendMode::Spv => Self::spv_connected(spv_status), + }; + self.overall_connected.store(connected, Ordering::Relaxed); + } + + pub fn overall_connected_with( + &self, + backend_mode: CoreBackendMode, + disable_zmq: bool, + spv_status: SpvStatus, + ) -> bool { + match backend_mode { + CoreBackendMode::Rpc => self.rpc_online() && (disable_zmq || self.zmq_connected()), + CoreBackendMode::Spv => Self::spv_connected(spv_status), + } + } + + pub fn tooltip_text(&self) -> String { + let backend_mode = self.backend_mode(); + let disable_zmq = self.disable_zmq(); + let spv_status = self.spv_status(); + match backend_mode { + CoreBackendMode::Rpc => { + let rpc_status = if self.rpc_online() { + "RPC: Connected" + } else { + "RPC: Disconnected" + }; + let zmq_status = if disable_zmq { + "ZMQ: Disabled" + } else if self.zmq_connected() { + "ZMQ: Connected" + } else { + "ZMQ: Disconnected" + }; + + if self.overall_connected() { + format!("Connected to Dash Core Wallet\n{rpc_status}\n{zmq_status}") + } else if self.rpc_online() { + format!("Dash Core connection incomplete\n{rpc_status}\n{zmq_status}") + } else { + format!( + "Disconnected from Dash Core Wallet. Click to start it.\n{rpc_status}\n{zmq_status}" + ) + } + } + CoreBackendMode::Spv => { + let spv_label = format!("SPV: {:?}", spv_status); + if self.overall_connected() { + format!("SPV connected\n{spv_label}") + } else { + format!("SPV disconnected\n{spv_label}") + } + } + } + } + + pub fn update_from_chainlocks( + &self, + network: Network, + mainnet_chainlock: &Option, + testnet_chainlock: &Option, + devnet_chainlock: &Option, + local_chainlock: &Option, + ) { + let online = match network { + Network::Dash => mainnet_chainlock.is_some(), + Network::Testnet => testnet_chainlock.is_some(), + Network::Devnet => devnet_chainlock.is_some(), + Network::Regtest => local_chainlock.is_some(), + _ => false, + }; + self.set_rpc_online(online); + } + + pub fn handle_task_result(&self, task_result: &TaskResult, active_network: Network) { + match task_result { + TaskResult::Success(message) => match message.as_ref() { + BackendTaskSuccessResult::CoreItem(CoreItem::ChainLocks( + mainnet_chainlock, + testnet_chainlock, + devnet_chainlock, + local_chainlock, + )) => { + self.update_from_chainlocks( + active_network, + mainnet_chainlock, + testnet_chainlock, + devnet_chainlock, + local_chainlock, + ); + self.refresh_overall(); + } + BackendTaskSuccessResult::CoreItem(CoreItem::ChainLock(_, network)) => { + if *network == active_network { + self.set_rpc_online(true); + self.refresh_overall(); + } + } + _ => {} + }, + TaskResult::Error(message) => { + if message.contains( + "Failed to get best chain lock for mainnet, testnet, devnet, and local", + ) { + self.set_rpc_online(false); + self.refresh_overall(); + } + } + _ => {} + } + } + + pub fn trigger_refresh(&self, app_context: &crate::context::AppContext) -> AppAction { + // throttle updates to once every 2 seconds + let mut last_update = match self.last_update.lock() { + Ok(guard) => guard, + Err(poisoned) => poisoned.into_inner(), + }; + let now = Instant::now(); + let timeout = if self.overall_connected() { + REFRESH_CONNECTED + } else { + REFRESH_DISCONNECTED + }; + if now.duration_since(*last_update) < timeout { + return AppAction::None; + } + *last_update = now; + + self.refersh_zmq_and_spv(app_context); + AppAction::BackendTask(BackendTask::CoreTask(CoreTask::GetBestChainLocks)) + } + + fn refersh_zmq_and_spv(&self, app_context: &crate::context::AppContext) { + // Get current backend mode + let backend_mode = app_context.core_backend_mode(); + self.set_backend_mode(backend_mode); + + if backend_mode == CoreBackendMode::Spv { + // SPV status is updated elsewhere + let spv_status = app_context.spv_manager().status().status; + self.set_spv_status(spv_status); + return; + } + + // just a safety check + if CoreBackendMode::Rpc != backend_mode { + tracing::error!( + "Unexpected backend mode in connection status refresh: {:?}", + backend_mode + ); + return; + } + + // Update ZMQ status if there's a new event + let disable_zmq = app_context + .get_settings() + .ok() + .flatten() + .map(|s| s.disable_zmq) + .unwrap_or(false); + self.set_disable_zmq(disable_zmq); + + if let Ok(event) = app_context.rx_zmq_status.try_recv() { + self.set_zmq_status(event); + } + + self.refresh_overall(); + } +} + +impl Default for ConnectionStatus { + fn default() -> Self { + Self::new() + } +} diff --git a/src/ui/components/top_panel.rs b/src/ui/components/top_panel.rs index 031817d92..b0a93a5b4 100644 --- a/src/ui/components/top_panel.rs +++ b/src/ui/components/top_panel.rs @@ -1,8 +1,8 @@ use crate::app::{AppAction, DesiredAppAction}; use crate::backend_task::BackendTask; use crate::backend_task::core::CoreTask; -use crate::components::core_zmq_listener::ZMQConnectionEvent; use crate::context::AppContext; +use crate::spv::CoreBackendMode; use crate::ui::ScreenType; use crate::ui::theme::{DashColors, Shadow, Shape}; use dash_sdk::dashcore_rpc::dashcore::Network; @@ -96,11 +96,9 @@ fn add_location_view(ui: &mut Ui, location: Vec<(&str, AppAction)>, dark_mode: b fn add_connection_indicator(ui: &mut Ui, app_context: &Arc) -> AppAction { let mut action = AppAction::None; - let connected = app_context - .zmq_connection_status - .lock() - .map(|status| matches!(*status, ZMQConnectionEvent::Connected)) - .unwrap_or(false); + let status = app_context.connection_status(); + let backend_mode = status.backend_mode(); + let connected = status.overall_connected(); // Get time for pulsating animation (only when connected) let pulse_scale = if connected { @@ -149,14 +147,13 @@ fn add_connection_indicator(ui: &mut Ui, app_context: &Arc) -> AppAc if connected { app_context.repaint_animation(ui.ctx()); } - let tip = if connected { - "Connected to Dash Core Wallet" - } else { - "Disconnected from Dash Core Wallet. Click to start it." - }; + let tip = status.tooltip_text(); let resp = resp.on_hover_text(tip); - if resp.clicked() && !connected { + if resp.clicked() + && backend_mode == CoreBackendMode::Rpc + && !status.rpc_online() + { let settings = app_context.get_settings().ok().flatten(); let (custom_path, overwrite) = settings diff --git a/src/ui/network_chooser_screen.rs b/src/ui/network_chooser_screen.rs index 527fafa98..75ff24a80 100644 --- a/src/ui/network_chooser_screen.rs +++ b/src/ui/network_chooser_screen.rs @@ -1,9 +1,10 @@ use crate::app::AppAction; -use crate::backend_task::core::{CoreItem, CoreTask}; +use crate::backend_task::core::CoreTask; use crate::backend_task::system_task::SystemTask; use crate::backend_task::{BackendTask, BackendTaskSuccessResult}; use crate::config::Config; use crate::context::AppContext; +use crate::context::connection_status::ConnectionStatus; use crate::model::wallet::DerivationPathHelpers; use crate::spv::{CoreBackendMode, SpvStatus, SpvStatusSnapshot}; use crate::ui::components::component_trait::Component; @@ -43,10 +44,6 @@ pub struct NetworkChooserScreen { pub local_app_context: Option>, pub local_network_dashmate_password: String, pub current_network: Network, - pub mainnet_core_status_online: bool, - pub testnet_core_status_online: bool, - pub devnet_core_status_online: bool, - pub local_core_status_online: bool, pub recheck_time: Option, custom_dash_qt_path: Option, custom_dash_qt_error_message: Option, @@ -141,10 +138,6 @@ impl NetworkChooserScreen { local_app_context: local_app_context.cloned(), local_network_dashmate_password, current_network, - mainnet_core_status_online: false, - testnet_core_status_online: false, - devnet_core_status_online: false, - local_core_status_online: false, recheck_time: None, custom_dash_qt_path, custom_dash_qt_error_message: None, @@ -450,20 +443,24 @@ impl NetworkChooserScreen { .entry(self.current_network) .or_insert(CoreBackendMode::Rpc); - // Check connection status - let (is_connected, snapshot) = match current_backend_mode { - CoreBackendMode::Rpc => (self.check_network_status(self.current_network), None), - CoreBackendMode::Spv => { - let ctx = self.current_app_context(); - let snap = ctx.spv_manager().status(); - let connected = snap.status.is_active() || snap.status == SpvStatus::Running; - (connected, Some(snap)) - } + let ctx = self.current_app_context(); + let status = ctx.connection_status(); + let disable_zmq = status.disable_zmq(); + let rpc_online = self.check_network_status(self.current_network); + let zmq_connected = status.zmq_connected(); + let spv_snapshot = ctx.spv_manager().status(); + let spv_status = status.spv_status(); + let spv_connected = ConnectionStatus::spv_connected(spv_status); + let snapshot = if current_backend_mode == CoreBackendMode::Spv { + Some(spv_snapshot.clone()) + } else { + None }; + let overall_connected = status.overall_connected(); // Button on the left with status ui.horizontal(|ui| { - if is_connected { + if overall_connected { if current_backend_mode == CoreBackendMode::Spv { let disconnect_button = egui::Button::new( egui::RichText::new("Disconnect").color(DashColors::WHITE), @@ -510,13 +507,22 @@ impl NetworkChooserScreen { } } else { // For Core mode, just show status since it can switch networks freely - ui.colored_label(DashColors::DASH_BLUE, "✅ Connected"); + let label = if disable_zmq { + "✅ Connected (RPC, ZMQ disabled)" + } else { + "✅ Connected (RPC + ZMQ)" + }; + ui.colored_label(DashColors::DASH_BLUE, label); } } else { // Don't show Connect button for Local network in RPC mode // (there's no Dash-Qt to start for local/regtest) - let show_connect_button = !(self.current_network == Network::Regtest - && current_backend_mode == CoreBackendMode::Rpc); + let show_connect_button = match current_backend_mode { + CoreBackendMode::Spv => true, + CoreBackendMode::Rpc => { + !rpc_online && self.current_network != Network::Regtest + } + }; if show_connect_button { let connect_button = egui::Button::new( @@ -552,6 +558,7 @@ impl NetworkChooserScreen { } } } + } }); @@ -568,6 +575,82 @@ impl NetworkChooserScreen { self.render_spv_sync_progress(ui, snap); } + + ui.add_space(10.0); + ui.separator(); + ui.add_space(10.0); + + ui.vertical(|ui| { + if current_backend_mode == CoreBackendMode::Rpc && !self.developer_mode { + ui.horizontal(|ui| { + ui.label("Core RPC:"); + let rpc_color = if rpc_online { + DashColors::SUCCESS + } else { + DashColors::ERROR + }; + let rpc_label = if rpc_online { "Connected" } else { "Disconnected" }; + ui.colored_label(rpc_color, rpc_label); + + ui.label(","); + ui.label("ZMQ:"); + if disable_zmq { + ui.colored_label(DashColors::text_secondary(dark_mode), "Disabled"); + } else { + let zmq_color = if zmq_connected { + DashColors::SUCCESS + } else { + DashColors::ERROR + }; + let zmq_label = if zmq_connected { "Connected" } else { "Disconnected" }; + ui.colored_label(zmq_color, zmq_label); + } + }); + } + + if current_backend_mode == CoreBackendMode::Rpc && self.developer_mode { + ui.horizontal(|ui| { + ui.label("Dash Core RPC:"); + let color = if rpc_online { + DashColors::SUCCESS + } else { + DashColors::ERROR + }; + let label = if rpc_online { "Connected" } else { "Disconnected" }; + ui.colored_label(color, label); + }); + + ui.horizontal(|ui| { + ui.label("ZMQ:"); + if disable_zmq { + ui.colored_label( + DashColors::text_secondary(dark_mode), + "Disabled", + ); + } else { + let color = if zmq_connected { + DashColors::SUCCESS + } else { + DashColors::ERROR + }; + let label = if zmq_connected { "Connected" } else { "Disconnected" }; + ui.colored_label(color, label); + } + }); + } + + if current_backend_mode == CoreBackendMode::Spv { + ui.horizontal(|ui| { + ui.label("SPV:"); + let color = if spv_connected { + DashColors::SUCCESS + } else { + DashColors::ERROR + }; + ui.colored_label(color, format!("{:?}", spv_status)); + }); + } + }); }); // Advanced Settings section with clean dropdown @@ -1700,10 +1783,22 @@ impl NetworkChooserScreen { /// Check if the network is working fn check_network_status(&self, network: Network) -> bool { match network { - Network::Dash => self.mainnet_core_status_online, - Network::Testnet => self.testnet_core_status_online, - Network::Devnet => self.devnet_core_status_online, - Network::Regtest => self.local_core_status_online, + Network::Dash => self.mainnet_app_context.connection_status().rpc_online(), + Network::Testnet => self + .testnet_app_context + .as_ref() + .map(|ctx| ctx.connection_status().rpc_online()) + .unwrap_or(false), + Network::Devnet => self + .devnet_app_context + .as_ref() + .map(|ctx| ctx.connection_status().rpc_online()) + .unwrap_or(false), + Network::Regtest => self + .local_app_context + .as_ref() + .map(|ctx| ctx.connection_status().rpc_online()) + .unwrap_or(false), _ => false, } } @@ -1811,40 +1906,11 @@ impl ScreenLike for NetworkChooserScreen { } fn display_message(&mut self, message: &str, _message_type: super::MessageType) { - if message.contains("Failed to get best chain lock for mainnet, testnet, devnet, and local") - { - self.mainnet_core_status_online = false; - self.testnet_core_status_online = false; - self.devnet_core_status_online = false; - self.local_core_status_online = false; - } + let _ = message; } fn display_task_result(&mut self, backend_task_success_result: BackendTaskSuccessResult) { - if let BackendTaskSuccessResult::CoreItem(CoreItem::ChainLocks( - mainnet_chainlock, - testnet_chainlock, - devnet_chainlock, - local_chainlock, - )) = backend_task_success_result - { - match mainnet_chainlock { - Some(_) => self.mainnet_core_status_online = true, - None => self.mainnet_core_status_online = false, - } - match testnet_chainlock { - Some(_) => self.testnet_core_status_online = true, - None => self.testnet_core_status_online = false, - } - match devnet_chainlock { - Some(_) => self.devnet_core_status_online = true, - None => self.devnet_core_status_online = false, - } - match local_chainlock { - Some(_) => self.local_core_status_online = true, - None => self.local_core_status_online = false, - } - } + let _ = backend_task_success_result; } fn ui(&mut self, ctx: &Context) -> AppAction { From 852135eb5ed5f3872592edc5a9591549e7eef38a Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 5 Feb 2026 17:13:52 +0100 Subject: [PATCH 2/8] chore: rabbit feedback --- src/context.rs | 2 +- src/context/connection_status.rs | 47 ++++++++++++++------------------ 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/src/context.rs b/src/context.rs index b0e1ab2b0..5db214c1e 100644 --- a/src/context.rs +++ b/src/context.rs @@ -19,11 +19,11 @@ use crate::model::wallet::{ }; use crate::sdk_wrapper::initialize_sdk; use crate::spv::{CoreBackendMode, SpvManager}; -use connection_status::ConnectionStatus; use crate::ui::RootScreenType; use crate::ui::tokens::tokens_screen::{IdentityTokenBalance, IdentityTokenIdentifier}; use crate::utils::tasks::TaskManager; use bincode::config; +use connection_status::ConnectionStatus; use crossbeam_channel::{Receiver, Sender}; use dash_sdk::Sdk; use dash_sdk::dashcore_rpc::dashcore::{InstantLock, Transaction}; diff --git a/src/context/connection_status.rs b/src/context/connection_status.rs index 0172409c0..c332aebb0 100644 --- a/src/context/connection_status.rs +++ b/src/context/connection_status.rs @@ -254,33 +254,26 @@ impl ConnectionStatus { let backend_mode = app_context.core_backend_mode(); self.set_backend_mode(backend_mode); - if backend_mode == CoreBackendMode::Spv { - // SPV status is updated elsewhere - let spv_status = app_context.spv_manager().status().status; - self.set_spv_status(spv_status); - return; - } - - // just a safety check - if CoreBackendMode::Rpc != backend_mode { - tracing::error!( - "Unexpected backend mode in connection status refresh: {:?}", - backend_mode - ); - return; - } - - // Update ZMQ status if there's a new event - let disable_zmq = app_context - .get_settings() - .ok() - .flatten() - .map(|s| s.disable_zmq) - .unwrap_or(false); - self.set_disable_zmq(disable_zmq); - - if let Ok(event) = app_context.rx_zmq_status.try_recv() { - self.set_zmq_status(event); + match backend_mode { + CoreBackendMode::Spv => { + // SPV status is updated elsewhere + let spv_status = app_context.spv_manager().status().status; + self.set_spv_status(spv_status); + } + CoreBackendMode::Rpc => { + // Update ZMQ status if there's a new event + let disable_zmq = app_context + .get_settings() + .ok() + .flatten() + .map(|s| s.disable_zmq) + .unwrap_or(false); + self.set_disable_zmq(disable_zmq); + + if let Ok(event) = app_context.rx_zmq_status.try_recv() { + self.set_zmq_status(event); + } + } } self.refresh_overall(); From bf49217e7170237d00f527b4676fde69dd52cb7e Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Mon, 9 Feb 2026 16:54:58 +0100 Subject: [PATCH 3/8] chore: typo + network changes --- src/context/connection_status.rs | 4 ++-- src/ui/network_chooser_screen.rs | 8 -------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/context/connection_status.rs b/src/context/connection_status.rs index c332aebb0..1e56cb79e 100644 --- a/src/context/connection_status.rs +++ b/src/context/connection_status.rs @@ -245,11 +245,11 @@ impl ConnectionStatus { } *last_update = now; - self.refersh_zmq_and_spv(app_context); + self.refresh_zmq_and_spv(app_context); AppAction::BackendTask(BackendTask::CoreTask(CoreTask::GetBestChainLocks)) } - fn refersh_zmq_and_spv(&self, app_context: &crate::context::AppContext) { + fn refresh_zmq_and_spv(&self, app_context: &crate::context::AppContext) { // Get current backend mode let backend_mode = app_context.core_backend_mode(); self.set_backend_mode(backend_mode); diff --git a/src/ui/network_chooser_screen.rs b/src/ui/network_chooser_screen.rs index 75ff24a80..e4412847b 100644 --- a/src/ui/network_chooser_screen.rs +++ b/src/ui/network_chooser_screen.rs @@ -1905,14 +1905,6 @@ impl ScreenLike for NetworkChooserScreen { } } - fn display_message(&mut self, message: &str, _message_type: super::MessageType) { - let _ = message; - } - - fn display_task_result(&mut self, backend_task_success_result: BackendTaskSuccessResult) { - let _ = backend_task_success_result; - } - fn ui(&mut self, ctx: &Context) -> AppAction { let mut action = add_top_panel( ctx, From 6a7251c8dbcf14bb9dc542bf010b989e0aa92087 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Mon, 9 Feb 2026 17:14:56 +0100 Subject: [PATCH 4/8] chore: apply feedback --- src/app.rs | 10 +++++ src/context.rs | 6 ++- src/context/connection_status.rs | 59 +++++++++++++----------------- src/spv/manager.rs | 30 +++++++++++---- src/ui/network_chooser_screen.rs | 30 ++++----------- src/ui/tokens/tokens_screen/mod.rs | 6 +-- 6 files changed, 72 insertions(+), 69 deletions(-) diff --git a/src/app.rs b/src/app.rs index 6283565e5..2cd015170 100644 --- a/src/app.rs +++ b/src/app.rs @@ -7,6 +7,7 @@ use crate::backend_task::core::CoreItem; use crate::backend_task::{BackendTask, BackendTaskSuccessResult}; use crate::components::core_zmq_listener::{CoreZMQListener, ZMQMessage}; use crate::context::AppContext; +use crate::context::connection_status::ConnectionStatus; use crate::database::Database; use crate::logging::initialize_logger; use crate::model::settings::Settings; @@ -65,6 +66,7 @@ pub struct AppState { pub selected_main_screen: RootScreenType, pub screen_stack: Vec, pub chosen_network: Network, + pub connection_status: Arc, pub mainnet_app_context: Arc, pub testnet_app_context: Option>, pub devnet_app_context: Option>, @@ -182,11 +184,13 @@ impl AppState { let onboarding_completed = settings.onboarding_completed; let subtasks = Arc::new(TaskManager::new()); + let connection_status = Arc::new(ConnectionStatus::new()); let mainnet_app_context = match AppContext::new( Network::Dash, db.clone(), password_info.clone(), subtasks.clone(), + connection_status.clone(), ) { Some(context) => context, None => { @@ -201,18 +205,21 @@ impl AppState { db.clone(), password_info.clone(), subtasks.clone(), + connection_status.clone(), ); let devnet_app_context = AppContext::new( Network::Devnet, db.clone(), password_info.clone(), subtasks.clone(), + connection_status.clone(), ); let local_app_context = AppContext::new( Network::Regtest, db.clone(), password_info, subtasks.clone(), + connection_status.clone(), ); // load fonts @@ -604,6 +611,7 @@ impl AppState { selected_main_screen, screen_stack: vec![], chosen_network, + connection_status, mainnet_app_context, testnet_app_context, devnet_app_context, @@ -738,6 +746,8 @@ impl AppState { for screen in self.main_screens.values_mut() { screen.change_context(app_context.clone()) } + + self.connection_status.reset(); } pub fn visible_screen_mut(&mut self) -> &mut Screen { diff --git a/src/context.rs b/src/context.rs index 5db214c1e..414c5a759 100644 --- a/src/context.rs +++ b/src/context.rs @@ -101,7 +101,8 @@ pub struct AppContext { pub(crate) subtasks: Arc, pub(crate) spv_manager: Arc, core_backend_mode: AtomicU8, - pub(crate) connection_status: ConnectionStatus, + /// Tracks the connection status to currently active network + pub(crate) connection_status: Arc, /// Pending wallet selection - set after creating/importing a wallet /// so the wallet screen can auto-select the new wallet pub(crate) pending_wallet_selection: Mutex>, @@ -120,6 +121,7 @@ impl AppContext { db: Arc, password_info: Option, subtasks: Arc, + connection_status: Arc, ) -> Option> { let config = match Config::load() { Ok(config) => config, @@ -277,7 +279,7 @@ impl AppContext { subtasks, spv_manager, core_backend_mode: AtomicU8::new(saved_core_backend_mode), - connection_status: ConnectionStatus::new(), + connection_status, pending_wallet_selection: Mutex::new(None), selected_wallet_hash: Mutex::new(selected_wallet_hash), selected_single_key_hash: Mutex::new(selected_single_key_hash), diff --git a/src/context/connection_status.rs b/src/context/connection_status.rs index 1e56cb79e..53feb3a49 100644 --- a/src/context/connection_status.rs +++ b/src/context/connection_status.rs @@ -12,6 +12,10 @@ use std::time::{Duration, Instant}; const REFRESH_CONNECTED: Duration = Duration::from_secs(10); const REFRESH_DISCONNECTED: Duration = Duration::from_secs(2); +/// Tracks the connection status to currently active network, and provides helper methods +/// to determine overall connectivity status. +/// +/// Supports Dash Core and SPV. #[derive(Debug)] pub struct ConnectionStatus { rpc_online: AtomicBool, @@ -36,6 +40,25 @@ impl ConnectionStatus { } } + /// Reset all connection state. Called when switching the active network + /// so the status reflects the new network from a clean slate. + pub fn reset(&self) { + self.rpc_online.store(false, Ordering::Relaxed); + if let Ok(mut status) = self.zmq_status.lock() { + *status = ZMQConnectionEvent::Disconnected; + } + self.spv_status + .store(SpvStatus::Idle as u8, Ordering::Relaxed); + self.backend_mode + .store(CoreBackendMode::Rpc.as_u8(), Ordering::Relaxed); + self.disable_zmq.store(false, Ordering::Relaxed); + self.overall_connected.store(false, Ordering::Relaxed); + // Set last_update to epoch so the next trigger_refresh fires immediately + if let Ok(mut last) = self.last_update.lock() { + *last = Instant::now() - REFRESH_CONNECTED; + } + } + pub fn rpc_online(&self) -> bool { self.rpc_online.load(Ordering::Relaxed) } @@ -58,15 +81,7 @@ impl ConnectionStatus { } pub fn spv_status(&self) -> SpvStatus { - match self.spv_status.load(Ordering::Relaxed) { - 1 => SpvStatus::Starting, - 2 => SpvStatus::Syncing, - 3 => SpvStatus::Running, - 4 => SpvStatus::Stopping, - 5 => SpvStatus::Stopped, - 6 => SpvStatus::Error, - _ => SpvStatus::Idle, - } + SpvStatus::from(self.spv_status.load(Ordering::Relaxed)) } pub fn set_spv_status(&self, status: SpvStatus) { @@ -90,19 +105,7 @@ impl ConnectionStatus { } pub fn spv_connected(status: SpvStatus) -> bool { - status.is_active() || status == SpvStatus::Running - } - - pub fn rpc_connected(&self) -> bool { - self.rpc_online() - } - - pub fn zmq_required(&self) -> bool { - !self.disable_zmq() - } - - pub fn rpc_zmq_healthy(&self) -> bool { - self.rpc_online() && (self.disable_zmq() || self.zmq_connected()) + status.is_active() } pub fn overall_connected(&self) -> bool { @@ -120,18 +123,6 @@ impl ConnectionStatus { self.overall_connected.store(connected, Ordering::Relaxed); } - pub fn overall_connected_with( - &self, - backend_mode: CoreBackendMode, - disable_zmq: bool, - spv_status: SpvStatus, - ) -> bool { - match backend_mode { - CoreBackendMode::Rpc => self.rpc_online() && (disable_zmq || self.zmq_connected()), - CoreBackendMode::Spv => Self::spv_connected(spv_status), - } - } - pub fn tooltip_text(&self) -> String { let backend_mode = self.backend_mode(); let disable_zmq = self.disable_zmq(); diff --git a/src/spv/manager.rs b/src/spv/manager.rs index ebad6b7ee..16dd0f4e0 100644 --- a/src/spv/manager.rs +++ b/src/spv/manager.rs @@ -56,15 +56,16 @@ impl From for CoreBackendMode { /// High-level status of the SPV client runtime. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[repr(u8)] pub enum SpvStatus { #[default] - Idle, - Starting, - Syncing, - Running, - Stopping, - Stopped, - Error, + Idle = 0, + Starting = 1, + Syncing = 2, + Running = 3, + Stopping = 4, + Stopped = 5, + Error = 6, } impl SpvStatus { @@ -76,6 +77,21 @@ impl SpvStatus { } } +impl From for SpvStatus { + fn from(value: u8) -> Self { + match value { + 0 => SpvStatus::Idle, + 1 => SpvStatus::Starting, + 2 => SpvStatus::Syncing, + 3 => SpvStatus::Running, + 4 => SpvStatus::Stopping, + 5 => SpvStatus::Stopped, + 6 => SpvStatus::Error, + _ => SpvStatus::Idle, + } + } +} + /// Snapshot of the SPV runtime state for UI consumption. /// Uses dash-spv's built-in progress types directly instead of duplicating. #[derive(Debug, Clone, Default)] diff --git a/src/ui/network_chooser_screen.rs b/src/ui/network_chooser_screen.rs index e4412847b..93422fccd 100644 --- a/src/ui/network_chooser_screen.rs +++ b/src/ui/network_chooser_screen.rs @@ -1,7 +1,7 @@ use crate::app::AppAction; use crate::backend_task::core::CoreTask; use crate::backend_task::system_task::SystemTask; -use crate::backend_task::{BackendTask, BackendTaskSuccessResult}; +use crate::backend_task::BackendTask; use crate::config::Config; use crate::context::AppContext; use crate::context::connection_status::ConnectionStatus; @@ -446,7 +446,7 @@ impl NetworkChooserScreen { let ctx = self.current_app_context(); let status = ctx.connection_status(); let disable_zmq = status.disable_zmq(); - let rpc_online = self.check_network_status(self.current_network); + let rpc_online = self.check_network_status(); let zmq_connected = status.zmq_connected(); let spv_snapshot = ctx.spv_manager().status(); let spv_status = status.spv_status(); @@ -1780,27 +1780,11 @@ impl NetworkChooserScreen { } } - /// Check if the network is working - fn check_network_status(&self, network: Network) -> bool { - match network { - Network::Dash => self.mainnet_app_context.connection_status().rpc_online(), - Network::Testnet => self - .testnet_app_context - .as_ref() - .map(|ctx| ctx.connection_status().rpc_online()) - .unwrap_or(false), - Network::Devnet => self - .devnet_app_context - .as_ref() - .map(|ctx| ctx.connection_status().rpc_online()) - .unwrap_or(false), - Network::Regtest => self - .local_app_context - .as_ref() - .map(|ctx| ctx.connection_status().rpc_online()) - .unwrap_or(false), - _ => false, - } + /// Check if the current network's RPC is online. + fn check_network_status(&self) -> bool { + self.current_app_context() + .connection_status() + .rpc_online() } fn any_rpc_backend(&self) -> bool { diff --git a/src/ui/tokens/tokens_screen/mod.rs b/src/ui/tokens/tokens_screen/mod.rs index 652feb847..ada097aa1 100644 --- a/src/ui/tokens/tokens_screen/mod.rs +++ b/src/ui/tokens/tokens_screen/mod.rs @@ -3233,7 +3233,7 @@ mod tests { db.initialize(Path::new(&db_file_path)).unwrap(); ensure_test_env(); - let app_context = AppContext::new(Network::Regtest, db, None, Default::default()) + let app_context = AppContext::new(Network::Regtest, db, None, Default::default(), Default::default()) .expect("Expected to create AppContext"); let mut token_creator_ui = TokensScreen::new(&app_context, TokensSubscreen::TokenCreator); @@ -3539,7 +3539,7 @@ mod tests { db.initialize(Path::new(&db_file_path)).unwrap(); ensure_test_env(); - let app_context = AppContext::new(Network::Regtest, db, None, Default::default()) + let app_context = AppContext::new(Network::Regtest, db, None, Default::default(), Default::default()) .expect("Expected to create AppContext"); let mut token_creator_ui = TokensScreen::new(&app_context, TokensSubscreen::TokenCreator); @@ -3659,7 +3659,7 @@ mod tests { db.initialize(Path::new(&db_file_path)).unwrap(); ensure_test_env(); - let app_context = AppContext::new(Network::Regtest, db, None, Default::default()) + let app_context = AppContext::new(Network::Regtest, db, None, Default::default(), Default::default()) .expect("Expected to create AppContext"); let mut token_creator_ui = TokensScreen::new(&app_context, TokensSubscreen::TokenCreator); From 5a07e24cd00cc65d6219c1236e2c774ee954c221 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Mon, 9 Feb 2026 17:37:41 +0100 Subject: [PATCH 5/8] chore: rabbit review --- src/app.rs | 3 ++- src/context/connection_status.rs | 7 +++++-- src/ui/network_chooser_screen.rs | 13 ++++--------- src/ui/tokens/tokens_screen/mod.rs | 30 ++++++++++++++++++++++++------ 4 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/app.rs b/src/app.rs index 2cd015170..4bf5ba5f8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -747,7 +747,8 @@ impl AppState { screen.change_context(app_context.clone()) } - self.connection_status.reset(); + self.connection_status + .reset(app_context.core_backend_mode()); } pub fn visible_screen_mut(&mut self) -> &mut Screen { diff --git a/src/context/connection_status.rs b/src/context/connection_status.rs index 53feb3a49..703adc869 100644 --- a/src/context/connection_status.rs +++ b/src/context/connection_status.rs @@ -42,7 +42,10 @@ impl ConnectionStatus { /// Reset all connection state. Called when switching the active network /// so the status reflects the new network from a clean slate. - pub fn reset(&self) { + /// + /// `backend_mode` should be the new network's current backend mode so that + /// `overall_connected()` and `tooltip_text()` read the correct mode immediately. + pub fn reset(&self, backend_mode: CoreBackendMode) { self.rpc_online.store(false, Ordering::Relaxed); if let Ok(mut status) = self.zmq_status.lock() { *status = ZMQConnectionEvent::Disconnected; @@ -50,7 +53,7 @@ impl ConnectionStatus { self.spv_status .store(SpvStatus::Idle as u8, Ordering::Relaxed); self.backend_mode - .store(CoreBackendMode::Rpc.as_u8(), Ordering::Relaxed); + .store(backend_mode.as_u8(), Ordering::Relaxed); self.disable_zmq.store(false, Ordering::Relaxed); self.overall_connected.store(false, Ordering::Relaxed); // Set last_update to epoch so the next trigger_refresh fires immediately diff --git a/src/ui/network_chooser_screen.rs b/src/ui/network_chooser_screen.rs index 93422fccd..d23b8065f 100644 --- a/src/ui/network_chooser_screen.rs +++ b/src/ui/network_chooser_screen.rs @@ -1,7 +1,7 @@ use crate::app::AppAction; +use crate::backend_task::BackendTask; use crate::backend_task::core::CoreTask; use crate::backend_task::system_task::SystemTask; -use crate::backend_task::BackendTask; use crate::config::Config; use crate::context::AppContext; use crate::context::connection_status::ConnectionStatus; @@ -446,7 +446,9 @@ impl NetworkChooserScreen { let ctx = self.current_app_context(); let status = ctx.connection_status(); let disable_zmq = status.disable_zmq(); - let rpc_online = self.check_network_status(); + let rpc_online = self.current_app_context() + .connection_status() + .rpc_online(); let zmq_connected = status.zmq_connected(); let spv_snapshot = ctx.spv_manager().status(); let spv_status = status.spv_status(); @@ -1780,13 +1782,6 @@ impl NetworkChooserScreen { } } - /// Check if the current network's RPC is online. - fn check_network_status(&self) -> bool { - self.current_app_context() - .connection_status() - .rpc_online() - } - fn any_rpc_backend(&self) -> bool { self.backend_modes .iter() diff --git a/src/ui/tokens/tokens_screen/mod.rs b/src/ui/tokens/tokens_screen/mod.rs index ada097aa1..c64536615 100644 --- a/src/ui/tokens/tokens_screen/mod.rs +++ b/src/ui/tokens/tokens_screen/mod.rs @@ -3233,8 +3233,14 @@ mod tests { db.initialize(Path::new(&db_file_path)).unwrap(); ensure_test_env(); - let app_context = AppContext::new(Network::Regtest, db, None, Default::default(), Default::default()) - .expect("Expected to create AppContext"); + let app_context = AppContext::new( + Network::Regtest, + db, + None, + Default::default(), + Default::default(), + ) + .expect("Expected to create AppContext"); let mut token_creator_ui = TokensScreen::new(&app_context, TokensSubscreen::TokenCreator); // Identity selection @@ -3539,8 +3545,14 @@ mod tests { db.initialize(Path::new(&db_file_path)).unwrap(); ensure_test_env(); - let app_context = AppContext::new(Network::Regtest, db, None, Default::default(), Default::default()) - .expect("Expected to create AppContext"); + let app_context = AppContext::new( + Network::Regtest, + db, + None, + Default::default(), + Default::default(), + ) + .expect("Expected to create AppContext"); let mut token_creator_ui = TokensScreen::new(&app_context, TokensSubscreen::TokenCreator); // Identity selection @@ -3659,8 +3671,14 @@ mod tests { db.initialize(Path::new(&db_file_path)).unwrap(); ensure_test_env(); - let app_context = AppContext::new(Network::Regtest, db, None, Default::default(), Default::default()) - .expect("Expected to create AppContext"); + let app_context = AppContext::new( + Network::Regtest, + db, + None, + Default::default(), + Default::default(), + ) + .expect("Expected to create AppContext"); let mut token_creator_ui = TokensScreen::new(&app_context, TokensSubscreen::TokenCreator); // Identity selection From 398dd7418921c082dd9d347a4661cad6057e432a Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Mon, 9 Feb 2026 18:40:16 +0100 Subject: [PATCH 6/8] chore: rabbit feedback --- src/ui/network_chooser_screen.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/ui/network_chooser_screen.rs b/src/ui/network_chooser_screen.rs index d23b8065f..9b8ed4ee2 100644 --- a/src/ui/network_chooser_screen.rs +++ b/src/ui/network_chooser_screen.rs @@ -446,15 +446,12 @@ impl NetworkChooserScreen { let ctx = self.current_app_context(); let status = ctx.connection_status(); let disable_zmq = status.disable_zmq(); - let rpc_online = self.current_app_context() - .connection_status() - .rpc_online(); + let rpc_online = status.rpc_online(); let zmq_connected = status.zmq_connected(); - let spv_snapshot = ctx.spv_manager().status(); let spv_status = status.spv_status(); let spv_connected = ConnectionStatus::spv_connected(spv_status); let snapshot = if current_backend_mode == CoreBackendMode::Spv { - Some(spv_snapshot.clone()) + Some(ctx.spv_manager().status().clone()) } else { None }; From 158f53e8d5176de870eb85927c5886c54f5ca434 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Mon, 9 Feb 2026 18:55:22 +0100 Subject: [PATCH 7/8] chore: rabbitting --- src/spv/manager.rs | 14 ++++++++++++++ src/ui/network_chooser_screen.rs | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/spv/manager.rs b/src/spv/manager.rs index 16dd0f4e0..9d5395664 100644 --- a/src/spv/manager.rs +++ b/src/spv/manager.rs @@ -77,6 +77,20 @@ impl SpvStatus { } } +impl std::fmt::Display for SpvStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SpvStatus::Idle => write!(f, "Idle"), + SpvStatus::Starting => write!(f, "Starting"), + SpvStatus::Syncing => write!(f, "Syncing"), + SpvStatus::Running => write!(f, "Running"), + SpvStatus::Stopping => write!(f, "Stopping"), + SpvStatus::Stopped => write!(f, "Stopped"), + SpvStatus::Error => write!(f, "Error"), + } + } +} + impl From for SpvStatus { fn from(value: u8) -> Self { match value { diff --git a/src/ui/network_chooser_screen.rs b/src/ui/network_chooser_screen.rs index 9b8ed4ee2..573117051 100644 --- a/src/ui/network_chooser_screen.rs +++ b/src/ui/network_chooser_screen.rs @@ -646,7 +646,7 @@ impl NetworkChooserScreen { } else { DashColors::ERROR }; - ui.colored_label(color, format!("{:?}", spv_status)); + ui.colored_label(color, spv_status.to_string()); }); } }); From ea2f1e51451e04be866a4e9a9dc0e37ba1098233 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Tue, 10 Feb 2026 16:04:03 +0100 Subject: [PATCH 8/8] chore: fmt --- src/context/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index 52ad2c695..a2e9000cd 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -10,7 +10,6 @@ pub(crate) use transaction_processing::get_transaction_info_via_dapi; use crate::app_dir::core_cookie_path; use crate::components::core_zmq_listener::ZMQConnectionEvent; use crate::config::{Config, NetworkConfig}; -use connection_status::ConnectionStatus; use crate::context_provider::Provider as RpcProvider; use crate::context_provider_spv::SpvProvider; use crate::database::Database; @@ -21,6 +20,7 @@ use crate::model::wallet::{Wallet, WalletSeedHash}; use crate::sdk_wrapper::initialize_sdk; use crate::spv::{CoreBackendMode, SpvManager}; use crate::utils::tasks::TaskManager; +use connection_status::ConnectionStatus; use crossbeam_channel::{Receiver, Sender}; use dash_sdk::Sdk; use dash_sdk::dashcore_rpc::{Auth, Client};