Skip to content
Open
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
10 changes: 5 additions & 5 deletions dash-spv/src/client/block_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,11 +220,11 @@ impl<W: WalletInterface, S: StorageManager> BlockProcessor<W, S> {

// Process block with wallet
let mut wallet = self.wallet.write().await;
let txids = wallet.process_block(&block, height).await;
if !txids.is_empty() {
let result = wallet.process_block(&block, height).await;
if !result.relevant_txids.is_empty() {
tracing::info!(
"🎯 Wallet found {} relevant transactions in block {} at height {}",
txids.len(),
result.relevant_txids.len(),
block_hash,
height
);
Expand All @@ -236,7 +236,7 @@ impl<W: WalletInterface, S: StorageManager> BlockProcessor<W, S> {
}

// Emit TransactionDetected events for each relevant transaction
for txid in &txids {
for txid in &result.relevant_txids {
if let Some(tx) = block.txdata.iter().find(|t| &t.txid() == txid) {
// Ask the wallet for the precise effect of this transaction
let effect = wallet.transaction_effect(tx).await;
Expand Down Expand Up @@ -269,7 +269,7 @@ impl<W: WalletInterface, S: StorageManager> BlockProcessor<W, S> {
height,
hash: block_hash.to_string(),
transactions_count: block.txdata.len(),
relevant_transactions: txids.len(),
relevant_transactions: result.relevant_txids.len(),
});

// Update chain state if needed
Expand Down
16 changes: 12 additions & 4 deletions dash-spv/src/client/block_processor_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod tests {
use crate::types::{SpvEvent, SpvStats};
use dashcore::{blockdata::constants::genesis_block, Block, Network, Transaction};

use key_wallet_manager::BlockProcessingResult;
use std::sync::Arc;
use tokio::sync::{mpsc, oneshot, Mutex, RwLock};

Expand Down Expand Up @@ -40,12 +41,15 @@ mod tests {

#[async_trait::async_trait]
impl key_wallet_manager::wallet_interface::WalletInterface for MockWallet {
async fn process_block(&mut self, block: &Block, height: u32) -> Vec<dashcore::Txid> {
async fn process_block(&mut self, block: &Block, height: u32) -> BlockProcessingResult {
let mut processed = self.processed_blocks.lock().await;
processed.push((block.block_hash(), height));

// Return txids of all transactions in block as "relevant"
block.txdata.iter().map(|tx| tx.txid()).collect()
BlockProcessingResult {
relevant_txids: block.txdata.iter().map(|tx| tx.txid()).collect(),
new_addresses: Vec::new(),
}
}

async fn process_mempool_transaction(&mut self, tx: &Transaction) {
Expand Down Expand Up @@ -248,8 +252,12 @@ mod tests {

#[async_trait::async_trait]
impl key_wallet_manager::wallet_interface::WalletInterface for NonMatchingWallet {
async fn process_block(&mut self, _block: &Block, _height: u32) -> Vec<dashcore::Txid> {
Vec::new()
async fn process_block(
&mut self,
_block: &Block,
_height: u32,
) -> BlockProcessingResult {
BlockProcessingResult::default()
}

async fn process_mempool_transaction(&mut self, _tx: &Transaction) {}
Expand Down
8 changes: 4 additions & 4 deletions dash-spv/src/sync/message_handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -745,18 +745,18 @@ impl<S: StorageManager, N: NetworkManager, W: WalletInterface> SyncManager<S, N,
.map_err(|e| SyncError::Storage(format!("Failed to get block height: {}", e)))?
.unwrap_or(0);

let relevant_txids = wallet.process_block(block, block_height).await;
let result = wallet.process_block(block, block_height).await;

drop(wallet);

if !relevant_txids.is_empty() {
if !result.relevant_txids.is_empty() {
tracing::info!(
"💰 Found {} relevant transactions in block {} at height {}",
relevant_txids.len(),
result.relevant_txids.len(),
block_hash,
block_height
);
for txid in &relevant_txids {
for txid in &result.relevant_txids {
tracing::debug!(" - Transaction: {}", txid);
}
}
Expand Down
4 changes: 2 additions & 2 deletions key-wallet-ffi/src/wallet_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -778,13 +778,13 @@ pub unsafe extern "C" fn wallet_manager_process_transaction(
let manager_ref = unsafe { &mut *manager };

// Process the transaction using async runtime
let relevant_wallets = manager_ref.runtime.block_on(async {
let result = manager_ref.runtime.block_on(async {
let mut manager_guard = manager_ref.manager.write().await;
manager_guard.check_transaction_in_all_wallets(&tx, context, update_state_if_found).await
});

FFIError::set_success(error);
!relevant_wallets.is_empty()
!result.affected_wallets.is_empty()
}

/// Get current height for a network
Expand Down
1 change: 1 addition & 0 deletions key-wallet-manager/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ bincode = { version = "=2.0.0-rc.3", optional = true }
zeroize = { version = "1.8", features = ["derive"] }

[dev-dependencies]
dashcore-test-utils = { path = "../test-utils" }
hex = "0.4"
serde_json = "1.0"
tokio = { version = "1.32", features = ["full"] }
Expand Down
1 change: 1 addition & 0 deletions key-wallet-manager/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ pub use key_wallet::wallet::managed_wallet_info::coin_selection::{
};
pub use key_wallet::wallet::managed_wallet_info::fee::{FeeEstimator, FeeRate};
pub use key_wallet::wallet::managed_wallet_info::transaction_builder::TransactionBuilder;
pub use wallet_interface::BlockProcessingResult;
pub use wallet_manager::{WalletError, WalletManager};
23 changes: 19 additions & 4 deletions key-wallet-manager/src/wallet_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,32 @@
//! This module defines the trait that SPV clients use to interact with wallets.

use alloc::string::String;
use alloc::vec::Vec;
use async_trait::async_trait;
use dashcore::bip158::BlockFilter;
use dashcore::prelude::CoreBlockHeight;
use dashcore::{Block, Transaction, Txid};
use dashcore::{Address, Block, Transaction, Txid};

/// Result of processing a block through the wallet
#[derive(Debug, Default, Clone)]
pub struct BlockProcessingResult {
/// Transaction IDs that were relevant to the wallet
pub relevant_txids: Vec<Txid>,
/// New addresses generated during gap limit maintenance
pub new_addresses: Vec<Address>,
}

/// Trait for wallet implementations to receive SPV events
#[async_trait]
pub trait WalletInterface: Send + Sync + 'static {
/// Called when a new block is received that may contain relevant transactions
/// Returns transaction IDs that were relevant to the wallet
async fn process_block(&mut self, block: &Block, height: CoreBlockHeight) -> Vec<Txid>;
/// Called when a new block is received that may contain relevant transactions.
/// Returns processing result including relevant transactions and any new addresses
/// generated during gap limit maintenance.
async fn process_block(
&mut self,
block: &Block,
height: CoreBlockHeight,
) -> BlockProcessingResult;

/// Called when a transaction is seen in the mempool
async fn process_mempool_transaction(&mut self, tx: &Transaction);
Expand Down
26 changes: 19 additions & 7 deletions key-wallet-manager/src/wallet_manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ pub struct AddressGenerationResult {
pub account_type_used: Option<AccountTypeUsed>,
}

/// Result of checking a transaction against all wallets
#[derive(Debug, Clone, Default)]
pub struct CheckTransactionsResult {
/// Wallets that found the transaction relevant
pub affected_wallets: Vec<WalletId>,
/// New addresses generated during gap limit maintenance
pub new_addresses: Vec<Address>,
}

/// High-level wallet manager that manages multiple wallets
///
/// Each wallet can contain multiple accounts following BIP44 standard.
Expand Down Expand Up @@ -450,14 +459,15 @@ impl<T: WalletInfoInterface> WalletManager<T> {
Ok(wallet_id)
}

/// Check a transaction against all wallets and update their states if relevant
/// Check a transaction against all wallets and update their states if relevant.
/// Returns affected wallets and any new addresses generated during gap limit maintenance.
pub async fn check_transaction_in_all_wallets(
&mut self,
tx: &Transaction,
context: TransactionContext,
update_state_if_found: bool,
) -> Vec<WalletId> {
let mut relevant_wallets = Vec::new();
) -> CheckTransactionsResult {
let mut result = CheckTransactionsResult::default();

// We need to iterate carefully since we're mutating
let wallet_ids: Vec<WalletId> = self.wallets.keys().cloned().collect();
Expand All @@ -469,18 +479,20 @@ impl<T: WalletInfoInterface> WalletManager<T> {
let wallet_info_opt = self.wallet_infos.get_mut(&wallet_id);

if let (Some(wallet), Some(wallet_info)) = (wallet_opt, wallet_info_opt) {
let result =
let check_result =
wallet_info.check_transaction(tx, context, wallet, update_state_if_found).await;

// If the transaction is relevant
if result.is_relevant {
relevant_wallets.push(wallet_id);
if check_result.is_relevant {
result.affected_wallets.push(wallet_id);
// Note: balance update is already handled in check_transaction
}

result.new_addresses.extend(check_result.new_addresses);
}
}

relevant_wallets
result
}

/// Create an account in a specific wallet
Expand Down
26 changes: 14 additions & 12 deletions key-wallet-manager/src/wallet_manager/process_block.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
use crate::wallet_interface::WalletInterface;
use crate::wallet_interface::{BlockProcessingResult, WalletInterface};
use crate::WalletManager;
use alloc::string::String;
use alloc::vec::Vec;
use async_trait::async_trait;
use core::fmt::Write as _;
use dashcore::bip158::BlockFilter;
use dashcore::prelude::CoreBlockHeight;
use dashcore::{Block, BlockHash, Transaction, Txid};
use dashcore::{Block, BlockHash, Transaction};
use key_wallet::transaction_checking::transaction_router::TransactionRouter;
use key_wallet::transaction_checking::TransactionContext;
use key_wallet::wallet::managed_wallet_info::wallet_info_interface::WalletInfoInterface;

#[async_trait]
impl<T: WalletInfoInterface + Send + Sync + 'static> WalletInterface for WalletManager<T> {
async fn process_block(&mut self, block: &Block, height: CoreBlockHeight) -> Vec<Txid> {
let mut relevant_txids = Vec::new();
async fn process_block(
&mut self,
block: &Block,
height: CoreBlockHeight,
) -> BlockProcessingResult {
let mut result = BlockProcessingResult::default();
let block_hash = Some(block.block_hash());
let timestamp = block.header.time;

Expand All @@ -26,20 +30,18 @@ impl<T: WalletInfoInterface + Send + Sync + 'static> WalletInterface for WalletM
timestamp: Some(timestamp),
};

let affected_wallets = self
.check_transaction_in_all_wallets(
tx, context, true, // update state
)
.await;
let check_result = self.check_transaction_in_all_wallets(tx, context, true).await;

if !affected_wallets.is_empty() {
relevant_txids.push(tx.txid());
if !check_result.affected_wallets.is_empty() {
result.relevant_txids.push(tx.txid());
}

result.new_addresses.extend(check_result.new_addresses);
}

self.current_height = height;

relevant_txids
result
}

async fn process_mempool_transaction(&mut self, tx: &Transaction) {
Expand Down
65 changes: 51 additions & 14 deletions key-wallet-manager/tests/spv_integration_tests.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
//! Integration tests for SPV wallet functionality

use dashcore::bip158::{BlockFilter, BlockFilterWriter};
use dashcore::blockdata::block::{Block, Header, Version};
use dashcore::blockdata::script::ScriptBuf;
use dashcore::blockdata::transaction::{OutPoint, Transaction};
use dashcore::blockdata::transaction::Transaction;
use dashcore::pow::CompactTarget;
use dashcore::{BlockHash, Txid};
use dashcore::{TxIn, TxOut};
use dashcore::{BlockHash, OutPoint, TxIn, TxOut, Txid};
use dashcore_hashes::Hash;

use dashcore::bip158::{BlockFilter, BlockFilterWriter};
use dashcore_test_utils::create_transaction_to_address;
use key_wallet::wallet::initialization::WalletAccountCreationOptions;
use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo;
use key_wallet::Network;
Expand Down Expand Up @@ -98,23 +97,61 @@ async fn test_filter_checking() {
#[tokio::test]
async fn test_block_processing() {
let mut manager = WalletManager::<ManagedWalletInfo>::new(Network::Testnet);

// Create a test wallet
let _wallet_id = manager
.create_wallet_with_random_mnemonic(WalletAccountCreationOptions::Default)
.expect("Failed to create wallet");

// Create a transaction
let tx = create_test_transaction(100000);
let addresses = manager.monitored_addresses();
assert!(!addresses.is_empty());
let external = dashcore::Address::p2pkh(
&dashcore::PublicKey::from_slice(&[0x02; 33]).expect("valid pubkey"),
Network::Testnet,
);

let addresses_before = manager.monitored_addresses();
assert!(!addresses_before.is_empty());

let tx1 = create_transaction_to_address(&addresses[0], 100_000);
let tx2 = create_transaction_to_address(&addresses[1], 200_000);
let tx3 = create_transaction_to_address(&external, 300_000);

let block = create_test_block(100, vec![tx1.clone(), tx2.clone(), tx3.clone()]);
let result = manager.process_block(&block, 100).await;

assert_eq!(result.relevant_txids.len(), 2);
assert!(result.relevant_txids.contains(&tx1.txid()));
assert!(result.relevant_txids.contains(&tx2.txid()));
assert!(!result.relevant_txids.contains(&tx3.txid()));
assert_eq!(result.new_addresses.len(), 2);

let addresses_after = manager.monitored_addresses();
let actual_increase = addresses_after.len() - addresses_before.len();
assert_eq!(result.new_addresses.len(), actual_increase);

for new_addr in &result.new_addresses {
assert!(addresses_after.contains(new_addr));
}
}

#[tokio::test]
async fn test_block_processing_result_empty() {
let mut manager = WalletManager::<ManagedWalletInfo>::new(Network::Testnet);
let _wallet_id = manager
.create_wallet_with_random_mnemonic(WalletAccountCreationOptions::Default)
.expect("Failed to create wallet");

// Create a block with this transaction
let block = create_test_block(100, vec![tx.clone()]);
let external = dashcore::Address::p2pkh(
&dashcore::PublicKey::from_slice(&[0x02; 33]).expect("valid pubkey"),
Network::Testnet,
);
let tx1 = create_transaction_to_address(&external, 100_000);
let tx2 = create_transaction_to_address(&external, 200_000);

// Process the block
let block = create_test_block(100, vec![tx1, tx2]);
let result = manager.process_block(&block, 100).await;

// Since we're not watching specific addresses, no transactions should be relevant
assert_eq!(result.len(), 0);
assert!(result.relevant_txids.is_empty());
assert!(result.new_addresses.is_empty());
}

#[tokio::test]
Expand Down
3 changes: 3 additions & 0 deletions key-wallet/src/transaction_checking/account_checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ pub struct TransactionCheckResult {
pub total_sent: u64,
/// Total value received for Platform credit conversion
pub total_received_for_credit_conversion: u64,
/// New addresses generated during gap limit maintenance
pub new_addresses: Vec<Address>,
}

/// Enum representing the type of account that matched with embedded data
Expand Down Expand Up @@ -284,6 +286,7 @@ impl ManagedAccountCollection {
total_received: 0,
total_sent: 0,
total_received_for_credit_conversion: 0,
new_addresses: Vec::new(),
};

for account_type in account_types {
Expand Down
Loading