diff --git a/dash-spv/src/main.rs b/dash-spv/src/main.rs index 4a21a6c32..37a875e73 100644 --- a/dash-spv/src/main.rs +++ b/dash-spv/src/main.rs @@ -224,7 +224,7 @@ async fn run() -> Result<(), Box> { // Create the wallet manager let mut wallet_manager = WalletManager::::new(); - wallet_manager.create_wallet_from_mnemonic( + let wallet_id = wallet_manager.create_wallet_from_mnemonic( "enemy check owner stumble unaware debris suffer peanut good fabric bleak outside", "", &[network], @@ -261,6 +261,7 @@ async fn run() -> Result<(), Box> { wallet, enable_terminal_ui, &matches, + wallet_id, ) .await?; } else { @@ -278,6 +279,7 @@ async fn run() -> Result<(), Box> { wallet, enable_terminal_ui, &matches, + wallet_id, ) .await?; } @@ -289,8 +291,16 @@ async fn run() -> Result<(), Box> { process::exit(1); } }; - run_client(config, network_manager, storage_manager, wallet, enable_terminal_ui, &matches) - .await?; + run_client( + config, + network_manager, + storage_manager, + wallet, + enable_terminal_ui, + &matches, + wallet_id, + ) + .await?; } Ok(()) @@ -303,6 +313,7 @@ async fn run_client>>, enable_terminal_ui: bool, matches: &clap::ArgMatches, + wallet_id: [u8; 32], ) -> Result<(), Box> { // Create and start the client let mut client = @@ -358,6 +369,100 @@ async fn run_client { + match maybe_event { + Some(SpvEvent::BlockProcessed { relevant_transactions, .. }) => { + if relevant_transactions > 0 { + total_detected_block_txs = total_detected_block_txs.saturating_add(relevant_transactions as u64); + tracing::info!( + "Detected {} wallet-relevant tx(s) in block; cumulative (blocks): {}", + relevant_transactions, + total_detected_block_txs + ); + } + } + Some(SpvEvent::MempoolTransactionAdded { .. }) => { + total_detected_mempool_txs = total_detected_mempool_txs.saturating_add(1); + tracing::info!( + "Detected wallet-relevant mempool tx; cumulative (mempool): {}", + total_detected_mempool_txs + ); + } + Some(_) => { /* ignore other events */ } + None => break, // sender closed + } + } + // Also do a periodic snapshot while events are flowing + _ = tokio::time::sleep(snapshot_interval) => { + // Log snapshot if interval has elapsed + if last_snapshot.elapsed() >= snapshot_interval { + let (tx_count, confirmed, unconfirmed, locked, total, derived_incoming) = { + let mgr = wallet_for_logger.read().await; + // Count transactions via network state for the selected network + let txs = mgr + .get_network_state(network_for_logger) + .map(|ns| ns.transactions.len()) + .unwrap_or(0); + + // Read wallet balance from the managed wallet info + let wb = mgr.get_wallet_balance(&wallet_id_for_logger).ok(); + let (c, u, l, t) = wb.map(|b| (b.confirmed, b.unconfirmed, b.locked, b.total)).unwrap_or((0, 0, 0, 0)); + + // Derive a conservative incoming total by summing tx outputs to our addresses. + let incoming_sum = if let Some(ns) = mgr.get_network_state(network_for_logger) { + let addrs = mgr.monitored_addresses(network_for_logger); + let addr_set: std::collections::HashSet<_> = addrs.into_iter().collect(); + let mut sum_incoming: u64 = 0; + for rec in ns.transactions.values() { + for out in &rec.transaction.output { + if let Ok(out_addr) = dashcore::Address::from_script(&out.script_pubkey, network_for_logger) { + if addr_set.contains(&out_addr) { + sum_incoming = sum_incoming.saturating_add(out.value); + } + } + } + } + sum_incoming + } else { 0 }; + + (txs, c, u, l, t, incoming_sum) + }; + tracing::info!( + "Wallet tx summary: detected={} (blocks={} + mempool={}), balances: confirmed={} unconfirmed={} locked={} total={}, derived_incoming_total={} (approx)", + tx_count, + total_detected_block_txs, + total_detected_mempool_txs, + confirmed, + unconfirmed, + locked, + total, + derived_incoming + ); + last_snapshot = std::time::Instant::now(); + } + } + } + } + }); + } else { + tracing::warn!("Event channel not available; transaction/balance logging disabled"); + } + // Add watch addresses if specified if let Some(addresses) = matches.get_many::("watch-address") { for addr_str in addresses { diff --git a/dash-spv/src/sync/headers_with_reorg.rs b/dash-spv/src/sync/headers_with_reorg.rs index e6d498a9d..400be5e7b 100644 --- a/dash-spv/src/sync/headers_with_reorg.rs +++ b/dash-spv/src/sync/headers_with_reorg.rs @@ -1122,10 +1122,15 @@ impl 0 { - self.total_headers_synced = sync_base_height + headers_len; + // Absolute blockchain tip height = base + headers_len - 1 (if any headers exist) + self.total_headers_synced = if headers_len == 0 { + if synced_from_checkpoint { + sync_base_height + } else { + 0 + } } else { - self.total_headers_synced = headers_len; - } + sync_base_height.saturating_add(headers_len).saturating_sub(1) + }; } } diff --git a/key-wallet/Cargo.toml b/key-wallet/Cargo.toml index 99d755fc3..817b141a2 100644 --- a/key-wallet/Cargo.toml +++ b/key-wallet/Cargo.toml @@ -42,7 +42,8 @@ serde_json = { version = "1.0", optional = true } hex = { version = "0.4"} hkdf = { version = "0.12", default-features = false } zeroize = { version = "1.8", features = ["derive"] } +tracing = "0.1" [dev-dependencies] hex = "0.4" -key-wallet = { path = ".", features = ["bip38", "serde", "bincode", "eddsa", "bls"] } \ No newline at end of file +key-wallet = { path = ".", features = ["bip38", "serde", "bincode", "eddsa", "bls"] } diff --git a/key-wallet/src/transaction_checking/wallet_checker.rs b/key-wallet/src/transaction_checking/wallet_checker.rs index ed675c79c..7c5b6fd8f 100644 --- a/key-wallet/src/transaction_checking/wallet_checker.rs +++ b/key-wallet/src/transaction_checking/wallet_checker.rs @@ -8,9 +8,10 @@ use super::transaction_router::TransactionRouter; use crate::wallet::immature_transaction::ImmatureTransaction; use crate::wallet::managed_wallet_info::wallet_info_interface::WalletInfoInterface; use crate::wallet::managed_wallet_info::ManagedWalletInfo; -use crate::{Network, Wallet}; +use crate::{Network, Utxo, Wallet}; use dashcore::blockdata::transaction::Transaction; use dashcore::BlockHash; +use dashcore::{Address as DashAddress, OutPoint}; use dashcore_hashes::Hash; /// Context for transaction processing @@ -187,6 +188,60 @@ impl WalletTransactionChecker for ManagedWalletInfo { account.transactions.insert(tx.txid(), tx_record); + // Ingest UTXOs for outputs that pay to our addresses and + // remove UTXOs that are spent by this transaction's inputs. + // Only apply for spendable account types (Standard, CoinJoin). + match &mut account.account_type { + crate::managed_account::managed_account_type::ManagedAccountType::Standard { .. } + | crate::managed_account::managed_account_type::ManagedAccountType::CoinJoin { .. } => { + // Build a set of addresses involved for fast membership tests + let mut involved_addrs = alloc::collections::BTreeSet::new(); + for info in account_match.account_type_match.all_involved_addresses() { + involved_addrs.insert(info.address.clone()); + } + + // Determine confirmation state and block height for UTXOs + let (is_confirmed, utxo_height) = match context { + TransactionContext::Mempool => (false, 0u32), + TransactionContext::InBlock { height, .. } + | TransactionContext::InChainLockedBlock { height, .. } => (true, height), + }; + + // Insert UTXOs for matching outputs + let txid = tx.txid(); + for (vout, output) in tx.output.iter().enumerate() { + if let Ok(addr) = DashAddress::from_script(&output.script_pubkey, network) { + if involved_addrs.contains(&addr) { + let outpoint = OutPoint { txid, vout: vout as u32 }; + // Construct TxOut clone explicitly to avoid trait assumptions + let txout = dashcore::TxOut { + value: output.value, + script_pubkey: output.script_pubkey.clone(), + }; + let mut utxo = Utxo::new( + outpoint, + txout, + addr, + utxo_height, + tx.is_coin_base(), + ); + utxo.is_confirmed = is_confirmed; + account.utxos.insert(outpoint, utxo); + } + } + } + + // Remove any UTXOs that are being spent by this transaction + for input in &tx.input { + // If this input spends one of our UTXOs, remove it + account.utxos.remove(&input.previous_output); + } + } + _ => { + // Skip UTXO ingestion for identity/provider accounts + } + } + // Mark involved addresses as used for address_info in account_match.account_type_match.all_involved_addresses() @@ -238,6 +293,31 @@ impl WalletTransactionChecker for ManagedWalletInfo { // Update cached balance self.update_balance(); + + // Emit a concise log for this detected transaction with net wallet change + let wallet_net: i64 = + (result.total_received as i64) - (result.total_sent as i64); + let ctx = match context { + TransactionContext::Mempool => "mempool".to_string(), + TransactionContext::InBlock { + height, + .. + } => alloc::format!("block {}", height), + TransactionContext::InChainLockedBlock { + height, + .. + } => { + alloc::format!("chainlocked block {}", height) + } + }; + tracing::info!( + txid = %tx.txid(), + context = %ctx, + net_change = wallet_net, + received = result.total_received, + sent = result.total_sent, + "Wallet transaction detected: net balance change" + ); } }