Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 108 additions & 3 deletions dash-spv/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {

// Create the wallet manager
let mut wallet_manager = WalletManager::<ManagedWalletInfo>::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],
Expand Down Expand Up @@ -261,6 +261,7 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {
wallet,
enable_terminal_ui,
&matches,
wallet_id,
)
.await?;
} else {
Expand All @@ -278,6 +279,7 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {
wallet,
enable_terminal_ui,
&matches,
wallet_id,
)
.await?;
}
Expand All @@ -289,8 +291,16 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {
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(())
Expand All @@ -303,6 +313,7 @@ async fn run_client<S: dash_spv::storage::StorageManager + Send + Sync + 'static
wallet: Arc<tokio::sync::RwLock<WalletManager<ManagedWalletInfo>>>,
enable_terminal_ui: bool,
matches: &clap::ArgMatches,
wallet_id: [u8; 32],
) -> Result<(), Box<dyn std::error::Error>> {
// Create and start the client
let mut client =
Expand Down Expand Up @@ -358,6 +369,100 @@ async fn run_client<S: dash_spv::storage::StorageManager + Send + Sync + 'static

tracing::info!("SPV client started successfully");

// Set up event logging: count detected transactions and log wallet balances periodically
// Take the client's event receiver and spawn a logger task
if let Some(mut event_rx) = client.take_event_receiver() {
let wallet_for_logger = wallet.clone();
let network_for_logger = config.network;
let wallet_id_for_logger = wallet_id;
tokio::spawn(async move {
use dash_spv::types::SpvEvent;
let mut total_detected_block_txs: u64 = 0;
let mut total_detected_mempool_txs: u64 = 0;
let mut last_snapshot = std::time::Instant::now();
let snapshot_interval = std::time::Duration::from_secs(10);

loop {
tokio::select! {
maybe_event = event_rx.recv() => {
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::<String>("watch-address") {
for addr_str in addresses {
Expand Down
13 changes: 9 additions & 4 deletions dash-spv/src/sync/headers_with_reorg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1122,10 +1122,15 @@ impl<S: StorageManager + Send + Sync + 'static, N: NetworkManager + Send + Sync
) {
self.cached_synced_from_checkpoint = synced_from_checkpoint;
self.cached_sync_base_height = sync_base_height;
if synced_from_checkpoint && sync_base_height > 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)
};
}
}
3 changes: 2 additions & 1 deletion key-wallet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
key-wallet = { path = ".", features = ["bip38", "serde", "bincode", "eddsa", "bls"] }
82 changes: 81 additions & 1 deletion key-wallet/src/transaction_checking/wallet_checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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());
}
Comment on lines +198 to +201
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Replace alloc:: uses (no extern crate alloc here).

Use std equivalents (BTreeSet, format!) to avoid build issues and simplify.

Apply:

-                                    let mut involved_addrs = alloc::collections::BTreeSet::new();
+                                    let mut involved_addrs = std::collections::BTreeSet::new();

and

-                    let ctx = match context {
-                        TransactionContext::Mempool => alloc::format!("mempool"),
+                    let ctx = match context {
+                        TransactionContext::Mempool => format!("mempool"),
                         TransactionContext::InBlock {
                             height,
                             ..
-                        } => alloc::format!("block {}", height),
+                        } => format!("block {}", height),
                         TransactionContext::InChainLockedBlock {
                             height,
                             ..
                         } => {
-                            alloc::format!("chainlocked block {}", height)
+                            format!("chainlocked block {}", height)
                         }
                     };

Optionally add at top:

use std::collections::BTreeSet; // if preferred

Also applies to: 301-312

🤖 Prompt for AI Agents
In key-wallet/src/transaction_checking/wallet_checker.rs around lines 198-201
(and similarly around 301-312), the code uses alloc::collections::BTreeSet and
alloc::format!, which fails because there is no extern crate alloc; replace
alloc::collections::BTreeSet::new() with std::collections::BTreeSet::new() (or
add use std::collections::BTreeSet; at the top and call BTreeSet::new()), and
replace alloc::format! with the standard format! macro; make the same
replacements in the other mentioned block (301-312).


// 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()
Expand Down Expand Up @@ -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"
);
}
}

Expand Down
Loading