Skip to content

Commit b732d2a

Browse files
feat: enhance clear_storage and add wallet balance to status display
This commit improves SPV client state management and monitoring: 1. Enhanced clear_storage() to fully reset in-memory state: - Resets chain state to network baseline - Clears sync manager filter state - Resets all statistics (peers, heights, downloads, etc.) - Clears received filter heights tracking - Resets mempool state and bloom filter - Previously only cleared on-disk storage 2. Added wallet balance display to status logs: - Shows balance in DASH denomination (8 decimal places) - Uses TypeId-based downcasting for WalletManager support - Balance displayed alongside sync progress metrics - Added filters_received count to status output 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent bba5e0c commit b732d2a

File tree

2 files changed

+137
-10
lines changed

2 files changed

+137
-10
lines changed

dash-spv/src/client/core.rs

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,68 @@ impl<
210210

211211
// ============ Storage Operations ============
212212

213-
/// Clear all persisted storage (headers, filters, state, sync state).
213+
/// Clear all persisted storage (headers, filters, state, sync state) and reset in-memory state.
214214
pub async fn clear_storage(&mut self) -> Result<()> {
215-
let mut storage = self.storage.lock().await;
216-
storage.clear().await.map_err(SpvError::Storage)
215+
// Wipe on-disk persistence fully
216+
{
217+
let mut storage = self.storage.lock().await;
218+
storage.clear().await.map_err(SpvError::Storage)?;
219+
}
220+
221+
// Reset in-memory chain state to a clean baseline for the current network
222+
{
223+
let mut state = self.state.write().await;
224+
*state = ChainState::new_for_network(self.config.network);
225+
}
226+
227+
// Reset sync manager filter state (headers/filters progress trackers)
228+
self.sync_manager.filter_sync_mut().clear_filter_state().await;
229+
230+
// Reset in-memory statistics and received filter height tracking without
231+
// replacing the SharedFilterHeights Arc (to keep existing references valid)
232+
let received_heights = {
233+
let stats = self.stats.read().await;
234+
stats.received_filter_heights.clone()
235+
};
236+
237+
{
238+
use std::time::Duration;
239+
let mut stats = self.stats.write().await;
240+
stats.connected_peers = 0;
241+
stats.total_peers = 0;
242+
stats.header_height = 0;
243+
stats.filter_height = 0;
244+
stats.headers_downloaded = 0;
245+
stats.filter_headers_downloaded = 0;
246+
stats.filters_downloaded = 0;
247+
stats.filters_matched = 0;
248+
stats.blocks_with_relevant_transactions = 0;
249+
stats.blocks_requested = 0;
250+
stats.blocks_processed = 0;
251+
stats.masternode_diffs_processed = 0;
252+
stats.bytes_received = 0;
253+
stats.bytes_sent = 0;
254+
stats.uptime = Duration::default();
255+
stats.filters_requested = 0;
256+
stats.filters_received = 0;
257+
stats.filter_sync_start_time = None;
258+
stats.last_filter_received_time = None;
259+
stats.active_filter_requests = 0;
260+
stats.pending_filter_requests = 0;
261+
stats.filter_request_timeouts = 0;
262+
stats.filter_requests_retried = 0;
263+
}
264+
265+
received_heights.lock().await.clear();
266+
267+
// Reset mempool tracking (state and bloom filter)
268+
{
269+
let mut mempool_state = self.mempool_state.write().await;
270+
*mempool_state = MempoolState::default();
271+
}
272+
self.mempool_filter = None;
273+
274+
Ok(())
217275
}
218276

219277
/// Clear only the persisted sync state snapshot (keep headers/filters).
@@ -295,20 +353,28 @@ impl<
295353

296354
/// Helper to create a StatusDisplay instance.
297355
#[cfg(feature = "terminal-ui")]
298-
pub(super) async fn create_status_display(&self) -> StatusDisplay<'_, S> {
356+
pub(super) async fn create_status_display(&self) -> StatusDisplay<'_, S, W> {
299357
StatusDisplay::new(
300358
&self.state,
301359
&self.stats,
302360
self.storage.clone(),
361+
Some(&self.wallet),
303362
&self.terminal_ui,
304363
&self.config,
305364
)
306365
}
307366

308367
/// Helper to create a StatusDisplay instance (without terminal UI).
309368
#[cfg(not(feature = "terminal-ui"))]
310-
pub(super) async fn create_status_display(&self) -> StatusDisplay<'_, S> {
311-
StatusDisplay::new(&self.state, &self.stats, self.storage.clone(), &None, &self.config)
369+
pub(super) async fn create_status_display(&self) -> StatusDisplay<'_, S, W> {
370+
StatusDisplay::new(
371+
&self.state,
372+
&self.stats,
373+
self.storage.clone(),
374+
Some(&self.wallet),
375+
&None,
376+
&self.config,
377+
)
312378
}
313379

314380
/// Update the status display.

dash-spv/src/client/status_display.rs

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,32 +9,38 @@ use crate::storage::StorageManager;
99
#[cfg(feature = "terminal-ui")]
1010
use crate::terminal::TerminalUI;
1111
use crate::types::{ChainState, SpvStats, SyncProgress};
12+
use key_wallet_manager::wallet_interface::WalletInterface;
1213

1314
/// Status display manager for updating UI and reporting sync progress.
14-
pub struct StatusDisplay<'a, S: StorageManager> {
15+
pub struct StatusDisplay<'a, S: StorageManager, W: WalletInterface> {
1516
state: &'a Arc<RwLock<ChainState>>,
1617
stats: &'a Arc<RwLock<SpvStats>>,
1718
storage: Arc<Mutex<S>>,
19+
wallet: Option<&'a Arc<RwLock<W>>>,
1820
#[cfg(feature = "terminal-ui")]
1921
terminal_ui: &'a Option<Arc<TerminalUI>>,
2022
#[allow(dead_code)]
2123
config: &'a ClientConfig,
2224
}
2325

24-
impl<'a, S: StorageManager + Send + Sync + 'static> StatusDisplay<'a, S> {
26+
impl<'a, S: StorageManager + Send + Sync + 'static, W: WalletInterface + Send + Sync + 'static>
27+
StatusDisplay<'a, S, W>
28+
{
2529
/// Create a new status display manager.
2630
#[cfg(feature = "terminal-ui")]
2731
pub fn new(
2832
state: &'a Arc<RwLock<ChainState>>,
2933
stats: &'a Arc<RwLock<SpvStats>>,
3034
storage: Arc<Mutex<S>>,
35+
wallet: Option<&'a Arc<RwLock<W>>>,
3136
terminal_ui: &'a Option<Arc<TerminalUI>>,
3237
config: &'a ClientConfig,
3338
) -> Self {
3439
Self {
3540
state,
3641
stats,
3742
storage,
43+
wallet,
3844
terminal_ui,
3945
config,
4046
}
@@ -46,13 +52,15 @@ impl<'a, S: StorageManager + Send + Sync + 'static> StatusDisplay<'a, S> {
4652
state: &'a Arc<RwLock<ChainState>>,
4753
stats: &'a Arc<RwLock<SpvStats>>,
4854
storage: Arc<Mutex<S>>,
55+
wallet: Option<&'a Arc<RwLock<W>>>,
4956
_terminal_ui: &'a Option<()>,
5057
config: &'a ClientConfig,
5158
) -> Self {
5259
Self {
5360
state,
5461
stats,
5562
storage,
63+
wallet,
5664
config,
5765
}
5866
}
@@ -140,6 +148,42 @@ impl<'a, S: StorageManager + Send + Sync + 'static> StatusDisplay<'a, S> {
140148
state.clone()
141149
}
142150

151+
/// Helper to try to get wallet balance if W implements Any.
152+
/// This is a wrapper that handles the case where W might not implement Any.
153+
async fn try_get_balance_if_any(wallet: &W) -> Option<u64>
154+
where
155+
W: 'static,
156+
{
157+
// Try to use Any trait for downcasting
158+
// We check if W is WalletManager<ManagedWalletInfo> using TypeId
159+
use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo;
160+
use key_wallet_manager::wallet_manager::WalletManager;
161+
use std::any::TypeId;
162+
163+
// Check if W is WalletManager<ManagedWalletInfo>
164+
let wallet_type_id = TypeId::of::<W>();
165+
let wallet_manager_type_id = TypeId::of::<WalletManager<ManagedWalletInfo>>();
166+
167+
if wallet_type_id == wallet_manager_type_id {
168+
// Unsafe downcast: we've verified the types match, so this is safe
169+
unsafe {
170+
let wallet_ptr = wallet as *const W as *const WalletManager<ManagedWalletInfo>;
171+
let wallet_ref = &*wallet_ptr;
172+
return Some(wallet_ref.get_total_balance());
173+
}
174+
}
175+
176+
None
177+
}
178+
179+
/// Format balance in DASH with 8 decimal places.
180+
fn format_balance(satoshis: u64) -> String {
181+
use dashcore::Amount;
182+
use dashcore::Denomination;
183+
let amount = Amount::from_sat(satoshis);
184+
amount.to_string_with_denomination(Denomination::Dash)
185+
}
186+
143187
/// Update the status display.
144188
pub async fn update_status_display(&self) {
145189
#[cfg(feature = "terminal-ui")]
@@ -219,23 +263,40 @@ impl<'a, S: StorageManager + Send + Sync + 'static> StatusDisplay<'a, S> {
219263

220264
// Get filter and block processing statistics
221265
let stats = self.stats.read().await;
266+
let filters_received = stats.filters_received;
222267
let filters_matched = stats.filters_matched;
223268
let blocks_with_relevant_transactions = stats.blocks_with_relevant_transactions;
224269
let blocks_processed = stats.blocks_processed;
225270
drop(stats);
226271

272+
// Get wallet balance if available
273+
let balance_str = if let Some(wallet_ref) = self.wallet {
274+
let wallet_guard = wallet_ref.read().await;
275+
// Try to get balance if W implements Any (for WalletManager support)
276+
// We use a helper that requires W: Any, so we need to handle this carefully
277+
// For now, we'll attempt to get balance only if possible
278+
Self::try_get_balance_if_any(&*wallet_guard)
279+
.await
280+
.map(|balance_sat| format!(" | Balance: {}", Self::format_balance(balance_sat)))
281+
.unwrap_or_default()
282+
} else {
283+
String::new()
284+
};
285+
227286
tracing::info!(
228-
"📊 [SYNC STATUS] Headers: {} | Filter Headers: {} | Latest ChainLock: {} | Filters Matched: {} | Blocks w/ Relevant Txs: {} | Blocks Processed: {}",
287+
"📊 [SYNC STATUS] Headers: {} | Filter Headers: {} | Filters: {} | Latest ChainLock: {} | Filters Matched: {} | Blocks w/ Relevant Txs: {} | Blocks Processed: {}{}",
229288
header_height,
230289
filter_height,
290+
filters_received,
231291
if chainlock_height > 0 {
232292
format!("#{}", chainlock_height)
233293
} else {
234294
"None".to_string()
235295
},
236296
filters_matched,
237297
blocks_with_relevant_transactions,
238-
blocks_processed
298+
blocks_processed,
299+
balance_str
239300
);
240301
}
241302
}

0 commit comments

Comments
 (0)