Skip to content

Commit 95aa697

Browse files
refactor: modularize SPV client and add wallet management functionality
This commit performs a major refactoring of the Dash SPV client to improve code organization, maintainability, and add comprehensive wallet management capabilities. Key Changes: - Split monolithic client code into focused modules: - block_processor.rs: Async block processing with dedicated worker thread - consistency.rs: Wallet consistency validation and recovery - wallet_utils.rs: Safe wallet operations with error handling - message_handler.rs: Network message processing logic - filter_sync.rs: Compact filter synchronization coordinator - status_display.rs: UI and progress reporting - watch_manager.rs: Watch item management - Added wallet integration: - Wallet-based UTXO tracking instead of direct storage manipulation - Address balance calculation through wallet - Wallet consistency checking and recovery mechanisms - Automatic wallet synchronization with watch items - Improved error handling: - Comprehensive error recovery in block processing - Safe UTXO operations with fallback behavior - Better error categorization and logging - Enhanced statistics tracking: - Separated filters_matched from blocks_with_relevant_transactions - More granular tracking of sync operations - Fixed transaction processing bugs: - Proper handling of multiple inputs from same address - Correct balance change calculations - Added test coverage for transaction calculation edge cases - Code quality improvements: - Reduced code duplication through helper methods - Better separation of concerns - More testable architecture This refactoring maintains backward compatibility while providing a cleaner architecture for future enhancements and easier maintenance.
1 parent 7735344 commit 95aa697

File tree

13 files changed

+2691
-1029
lines changed

13 files changed

+2691
-1029
lines changed

dash-spv/src/client/block_processor.rs

Lines changed: 427 additions & 0 deletions
Large diffs are not rendered by default.

dash-spv/src/client/consistency.rs

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
//! Wallet consistency validation and recovery functionality.
2+
3+
use std::sync::Arc;
4+
use tokio::sync::RwLock;
5+
use std::collections::HashSet;
6+
7+
use crate::error::{Result, SpvError};
8+
use crate::types::WatchItem;
9+
use crate::wallet::Wallet;
10+
use crate::storage::StorageManager;
11+
12+
/// Report of wallet consistency validation.
13+
#[derive(Debug, Clone)]
14+
pub struct ConsistencyReport {
15+
/// UTXO mismatches between wallet and storage.
16+
pub utxo_mismatches: Vec<String>,
17+
/// Address mismatches between watch items and wallet.
18+
pub address_mismatches: Vec<String>,
19+
/// Balance calculation mismatches.
20+
pub balance_mismatches: Vec<String>,
21+
/// Whether the wallet and storage are consistent.
22+
pub is_consistent: bool,
23+
}
24+
25+
/// Result of wallet consistency recovery attempt.
26+
#[derive(Debug, Clone)]
27+
pub struct ConsistencyRecovery {
28+
/// Number of UTXOs synced from storage to wallet.
29+
pub utxos_synced: usize,
30+
/// Number of addresses synced between watch items and wallet.
31+
pub addresses_synced: usize,
32+
/// Number of UTXOs removed from wallet (not in storage).
33+
pub utxos_removed: usize,
34+
/// Whether the recovery was successful.
35+
pub success: bool,
36+
}
37+
38+
/// Wallet consistency manager.
39+
pub struct ConsistencyManager<'a> {
40+
wallet: &'a Arc<RwLock<Wallet>>,
41+
storage: &'a dyn StorageManager,
42+
watch_items: &'a Arc<RwLock<HashSet<WatchItem>>>,
43+
}
44+
45+
impl<'a> ConsistencyManager<'a> {
46+
/// Create a new consistency manager.
47+
pub fn new(
48+
wallet: &'a Arc<RwLock<Wallet>>,
49+
storage: &'a dyn StorageManager,
50+
watch_items: &'a Arc<RwLock<HashSet<WatchItem>>>,
51+
) -> Self {
52+
Self {
53+
wallet,
54+
storage,
55+
watch_items,
56+
}
57+
}
58+
59+
/// Validate wallet and storage consistency.
60+
pub async fn validate_wallet_consistency(&self) -> Result<ConsistencyReport> {
61+
tracing::info!("Validating wallet and storage consistency...");
62+
63+
let mut report = ConsistencyReport {
64+
utxo_mismatches: Vec::new(),
65+
address_mismatches: Vec::new(),
66+
balance_mismatches: Vec::new(),
67+
is_consistent: true,
68+
};
69+
70+
// Validate UTXO consistency between wallet and storage
71+
let wallet = self.wallet.read().await;
72+
let wallet_utxos = wallet.get_utxos().await;
73+
let storage_utxos = self.storage.get_all_utxos().await
74+
.map_err(|e| SpvError::Storage(e))?;
75+
76+
// Check for UTXOs in wallet but not in storage
77+
for wallet_utxo in &wallet_utxos {
78+
if !storage_utxos.contains_key(&wallet_utxo.outpoint) {
79+
report.utxo_mismatches.push(format!(
80+
"UTXO {} exists in wallet but not in storage",
81+
wallet_utxo.outpoint
82+
));
83+
report.is_consistent = false;
84+
}
85+
}
86+
87+
// Check for UTXOs in storage but not in wallet
88+
for (outpoint, storage_utxo) in &storage_utxos {
89+
if !wallet_utxos.iter().any(|wu| &wu.outpoint == outpoint) {
90+
report.utxo_mismatches.push(format!(
91+
"UTXO {} exists in storage but not in wallet (address: {})",
92+
outpoint, storage_utxo.address
93+
));
94+
report.is_consistent = false;
95+
}
96+
}
97+
98+
// Validate address consistency between WatchItems and wallet
99+
let watch_items = self.watch_items.read().await;
100+
let wallet_addresses = wallet.get_watched_addresses().await;
101+
102+
// Collect addresses from watch items
103+
let watch_addresses: std::collections::HashSet<_> = watch_items.iter()
104+
.filter_map(|item| {
105+
if let WatchItem::Address { address, .. } = item {
106+
Some(address.clone())
107+
} else {
108+
None
109+
}
110+
})
111+
.collect();
112+
113+
let wallet_address_set: std::collections::HashSet<_> = wallet_addresses.iter().cloned().collect();
114+
115+
// Check for addresses in watch items but not in wallet
116+
for address in &watch_addresses {
117+
if !wallet_address_set.contains(address) {
118+
report.address_mismatches.push(format!(
119+
"Address {} in watch items but not in wallet",
120+
address
121+
));
122+
report.is_consistent = false;
123+
}
124+
}
125+
126+
// Check for addresses in wallet but not in watch items
127+
for address in &wallet_addresses {
128+
if !watch_addresses.contains(address) {
129+
report.address_mismatches.push(format!(
130+
"Address {} in wallet but not in watch items",
131+
address
132+
));
133+
report.is_consistent = false;
134+
}
135+
}
136+
137+
if report.is_consistent {
138+
tracing::info!("✅ Wallet consistency validation passed");
139+
} else {
140+
tracing::warn!("❌ Wallet consistency issues detected: {} UTXO mismatches, {} address mismatches",
141+
report.utxo_mismatches.len(), report.address_mismatches.len());
142+
}
143+
144+
Ok(report)
145+
}
146+
147+
/// Attempt to recover from wallet consistency issues.
148+
pub async fn recover_wallet_consistency(&self) -> Result<ConsistencyRecovery> {
149+
tracing::info!("Attempting wallet consistency recovery...");
150+
151+
let mut recovery = ConsistencyRecovery {
152+
utxos_synced: 0,
153+
addresses_synced: 0,
154+
utxos_removed: 0,
155+
success: true,
156+
};
157+
158+
// First, validate to see what needs fixing
159+
let report = self.validate_wallet_consistency().await?;
160+
161+
if report.is_consistent {
162+
tracing::info!("No recovery needed - wallet is already consistent");
163+
return Ok(recovery);
164+
}
165+
166+
let wallet = self.wallet.read().await;
167+
168+
// Sync UTXOs from storage to wallet
169+
let storage_utxos = self.storage.get_all_utxos().await
170+
.map_err(|e| SpvError::Storage(e))?;
171+
let wallet_utxos = wallet.get_utxos().await;
172+
173+
// Add missing UTXOs to wallet
174+
for (outpoint, storage_utxo) in &storage_utxos {
175+
if !wallet_utxos.iter().any(|wu| &wu.outpoint == outpoint) {
176+
if let Err(e) = wallet.add_utxo(storage_utxo.clone()).await {
177+
tracing::error!("Failed to sync UTXO {} to wallet: {}", outpoint, e);
178+
recovery.success = false;
179+
} else {
180+
recovery.utxos_synced += 1;
181+
}
182+
}
183+
}
184+
185+
// Remove UTXOs from wallet that aren't in storage
186+
for wallet_utxo in &wallet_utxos {
187+
if !storage_utxos.contains_key(&wallet_utxo.outpoint) {
188+
if let Err(e) = wallet.remove_utxo(&wallet_utxo.outpoint).await {
189+
tracing::error!("Failed to remove UTXO {} from wallet: {}", wallet_utxo.outpoint, e);
190+
recovery.success = false;
191+
} else {
192+
recovery.utxos_removed += 1;
193+
}
194+
}
195+
}
196+
197+
if recovery.success {
198+
tracing::info!("✅ Wallet consistency recovery completed: {} UTXOs synced, {} UTXOs removed, {} addresses synced",
199+
recovery.utxos_synced, recovery.utxos_removed, recovery.addresses_synced);
200+
} else {
201+
tracing::error!("❌ Wallet consistency recovery partially failed");
202+
}
203+
204+
Ok(recovery)
205+
}
206+
207+
/// Ensure wallet consistency by validating and recovering if necessary.
208+
pub async fn ensure_wallet_consistency(&self) -> Result<()> {
209+
// First validate consistency
210+
let report = self.validate_wallet_consistency().await?;
211+
212+
if !report.is_consistent {
213+
tracing::warn!("Wallet inconsistencies detected, attempting recovery...");
214+
215+
// Attempt recovery
216+
let recovery = self.recover_wallet_consistency().await?;
217+
218+
if !recovery.success {
219+
return Err(SpvError::Config(
220+
"Wallet consistency recovery failed - some issues remain".to_string()
221+
));
222+
}
223+
224+
// Validate again after recovery
225+
let post_recovery_report = self.validate_wallet_consistency().await?;
226+
if !post_recovery_report.is_consistent {
227+
return Err(SpvError::Config(
228+
"Wallet consistency recovery incomplete - issues remain after recovery".to_string()
229+
));
230+
}
231+
232+
tracing::info!("✅ Wallet consistency fully recovered");
233+
}
234+
235+
Ok(())
236+
}
237+
}

0 commit comments

Comments
 (0)