diff --git a/dash-spv-ffi/src/callbacks.rs b/dash-spv-ffi/src/callbacks.rs index 62e25f8e0..ff1d1c765 100644 --- a/dash-spv-ffi/src/callbacks.rs +++ b/dash-spv-ffi/src/callbacks.rs @@ -362,7 +362,7 @@ impl FFISyncEventCallbacks { SyncEvent::BlockProcessed { block_hash, height, - new_addresses, + new_scripts, confirmed_txids, .. } => { @@ -370,11 +370,11 @@ impl FFISyncEventCallbacks { let hash_bytes = block_hash.as_byte_array(); let txid_bytes: Vec<[u8; 32]> = confirmed_txids.iter().map(|txid| *txid.as_byte_array()).collect(); - let total_new_addresses: usize = new_addresses.values().map(|v| v.len()).sum(); + let total_new_scripts: usize = new_scripts.values().map(|v| v.len()).sum(); cb( *height, hash_bytes as *const [u8; 32], - total_new_addresses as u32, + total_new_scripts as u32, txid_bytes.as_ptr(), txid_bytes.len() as u32, self.user_data, @@ -1185,7 +1185,7 @@ impl FFIWalletEventCallbacks { mod tests { use super::*; use dashcore::hashes::Hash; - use dashcore::{Address, BlockHash, ChainLock, Network, Txid}; + use dashcore::{Address, BlockHash, ChainLock, Network, ScriptBuf, Txid}; use key_wallet_manager::{FilterMatchKey, WalletId}; use std::collections::{BTreeMap, BTreeSet}; use std::sync::atomic::{AtomicU32, Ordering}; @@ -1247,16 +1247,16 @@ mod tests { let addr_a = Address::dummy(Network::Regtest, 1); let addr_b = Address::dummy(Network::Regtest, 2); let addr_c = Address::dummy(Network::Regtest, 3); - let mut new_addresses: BTreeMap> = BTreeMap::new(); - // Wallet 1 contributes 2 new addresses, wallet 2 contributes 1. Total = 3. - new_addresses.insert([1u8; 32], vec![addr_a, addr_b]); - new_addresses.insert([2u8; 32], vec![addr_c]); + let mut new_scripts: BTreeMap> = BTreeMap::new(); + // Wallet 1 contributes 2 new scripts, wallet 2 contributes 1. Total = 3. + new_scripts.insert([1u8; 32], vec![addr_a.script_pubkey(), addr_b.script_pubkey()]); + new_scripts.insert([2u8; 32], vec![addr_c.script_pubkey()]); callbacks.dispatch(&SyncEvent::BlockProcessed { block_hash: BlockHash::from_byte_array([7u8; 32]), height: 100, wallets: BTreeSet::new(), - new_addresses, + new_scripts, confirmed_txids: vec![Txid::from_byte_array([9u8; 32])], }); assert_eq!(NEW_ADDR_COUNT.load(Ordering::SeqCst), 3); diff --git a/dash-spv/src/sync/blocks/manager.rs b/dash-spv/src/sync/blocks/manager.rs index a1fd8c51f..da9e01220 100644 --- a/dash-spv/src/sync/blocks/manager.rs +++ b/dash-spv/src/sync/blocks/manager.rs @@ -89,30 +89,30 @@ impl BlocksManager 0 { tracing::info!( - "Found {} relevant transactions ({} new, {} existing) {} at height {}, new addresses: {}", + "Found {} relevant transactions ({} new, {} existing) {} at height {}, new scripts: {}", total_relevant, result.new_txids.len(), result.existing_txids.len(), hash, height, - new_addresses_total + new_scripts_total ); } - // Collect confirmed txids before moving new_addresses out of result + // Collect confirmed txids before moving new_scripts out of result let confirmed_txids: Vec<_> = result.relevant_txids().cloned().collect(); - // Collect new addresses for gap limit rescanning - let new_addresses = result.new_addresses; - if new_addresses_total > 0 { + // Collect new scripts for gap limit rescanning + let new_scripts = result.new_scripts; + if new_scripts_total > 0 { tracing::debug!( - "Block {} generated {} new addresses for gap limit maintenance across {} wallets", + "Block {} generated {} new scripts for gap limit maintenance across {} wallets", height, - new_addresses_total, - new_addresses.len() + new_scripts_total, + new_scripts.len() ); } @@ -128,7 +128,7 @@ impl BlocksManager, - /// New addresses discovered from wallet gap limit maintenance, attributed - /// to the wallet that produced them. - new_addresses: BTreeMap>, + /// Cached scriptPubKeys for addresses freshly derived via wallet + /// gap-limit maintenance, attributed to the wallet that produced them. + new_scripts: BTreeMap>, /// Transaction IDs confirmed in this block that are relevant to the wallet confirmed_txids: Vec, }, @@ -214,11 +214,11 @@ impl fmt::Display for SyncEvent { } => write!(f, "BlocksNeeded(count={})", blocks.len()), SyncEvent::BlockProcessed { height, - new_addresses, + new_scripts, .. } => { - let total: usize = new_addresses.values().map(|v| v.len()).sum(); - write!(f, "BlockProcessed(height={}, new_addrs={})", height, total) + let total: usize = new_scripts.values().map(|v| v.len()).sum(); + write!(f, "BlockProcessed(height={}, new_scripts={})", height, total) } SyncEvent::MasternodeStateUpdated { height, diff --git a/dash-spv/src/sync/filters/batch.rs b/dash-spv/src/sync/filters/batch.rs index 02b80be12..386e0d16e 100644 --- a/dash-spv/src/sync/filters/batch.rs +++ b/dash-spv/src/sync/filters/batch.rs @@ -1,5 +1,5 @@ use dashcore::bip158::BlockFilter; -use dashcore::Address; +use dashcore::ScriptBuf; use key_wallet_manager::{FilterMatchKey, WalletId}; use std::collections::{BTreeSet, HashMap, HashSet}; @@ -28,10 +28,10 @@ pub(super) struct FiltersBatch { /// therefore need their `synced_height` advanced when the batch commits. /// Already-synced wallets must not be touched. scanned_wallets: BTreeSet, - /// Addresses discovered during block processing that still need rescan, - /// attributed per wallet so we can rerun matching only against the wallet - /// that produced each new address. - collected_addresses: HashMap>, + /// Cached scriptPubKeys discovered during block processing that still + /// need rescan, attributed per wallet so we can rerun matching only + /// against the wallet that produced each new script. + collected_scripts: HashMap>, } impl FiltersBatch { @@ -50,7 +50,7 @@ impl FiltersBatch { pending_blocks: 0, rescan_complete: false, scanned_wallets: BTreeSet::new(), - collected_addresses: HashMap::new(), + collected_scripts: HashMap::new(), } } /// Start height of this batch (inclusive). @@ -106,17 +106,17 @@ impl FiltersBatch { pub(super) fn mark_rescan_complete(&mut self) { self.rescan_complete = true; } - /// Add addresses discovered during block processing for later rescan. - pub(super) fn add_addresses_for_wallet( + /// Add scriptPubKeys discovered during block processing for later rescan. + pub(super) fn add_scripts_for_wallet( &mut self, wallet_id: WalletId, - addresses: impl IntoIterator, + scripts: impl IntoIterator, ) { - self.collected_addresses.entry(wallet_id).or_default().extend(addresses); + self.collected_scripts.entry(wallet_id).or_default().extend(scripts); } - /// Take collected per-wallet addresses for rescan, leaving the map empty. - pub(super) fn take_collected_addresses(&mut self) -> HashMap> { - std::mem::take(&mut self.collected_addresses) + /// Take collected per-wallet scripts for rescan, leaving the map empty. + pub(super) fn take_collected_scripts(&mut self) -> HashMap> { + std::mem::take(&mut self.collected_scripts) } /// Record the set of wallets that were behind for this batch at scan time. pub(super) fn set_scanned_wallets(&mut self, wallets: BTreeSet) { diff --git a/dash-spv/src/sync/filters/manager.rs b/dash-spv/src/sync/filters/manager.rs index 6bf722251..2464466b2 100644 --- a/dash-spv/src/sync/filters/manager.rs +++ b/dash-spv/src/sync/filters/manager.rs @@ -8,7 +8,7 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::sync::Arc; use dashcore::bip158::BlockFilter; -use dashcore::Address; +use dashcore::ScriptBuf; use super::batch::FiltersBatch; use super::block_match_tracker::{BlockMatchTracker, BlockTrackResult}; @@ -23,7 +23,7 @@ use crate::validation::{FilterValidationInput, FilterValidator, Validator}; use crate::sync::progress::ProgressPercentage; use dashcore::hash_types::FilterHeader; use key_wallet_manager::WalletInterface; -use key_wallet_manager::{check_compact_filters_for_addresses, FilterMatchKey, WalletId}; +use key_wallet_manager::{check_compact_filters_for_script_pubkeys, FilterMatchKey, WalletId}; use tokio::sync::RwLock; /// Batch size for processing filters. @@ -467,16 +467,16 @@ impl = self @@ -487,7 +487,7 @@ impl>, + new_scripts: &HashMap>, ) -> SyncResult> { - if new_addresses.is_empty() { + if new_scripts.is_empty() { return Ok(vec![]); } @@ -625,10 +625,10 @@ impl = { let wallet = self.wallet.read().await; - new_addresses.keys().map(|id| (*id, wallet.wallet_synced_height(id))).collect() + new_scripts.keys().map(|id| (*id, wallet.wallet_synced_height(id))).collect() }; let mut block_to_wallets: BTreeMap> = BTreeMap::new(); - for (wallet_id, addresses) in new_addresses { - if addresses.is_empty() { + for (wallet_id, scripts) in new_scripts { + if scripts.is_empty() { continue; } - let addresses_vec: Vec<_> = addresses.iter().cloned().collect(); + let scripts_vec: Vec = scripts.iter().cloned().collect(); let min_synced = synced_heights.get(wallet_id).copied().unwrap_or(0); let matches = - check_compact_filters_for_addresses(batch_filters, addresses_vec, min_synced); + check_compact_filters_for_script_pubkeys(batch_filters, &scripts_vec, min_synced); for key in matches { block_to_wallets.entry(key).or_default().insert(*wallet_id); } @@ -724,16 +724,16 @@ impl= batch_end` is fully covered and is - // skipped entirely, its addresses never even get tested against these + // skipped entirely, its scripts never even get tested against these // filters. let wallet = self.wallet.read().await; let behind = wallet.wallets_behind(batch_end); - let mut wallet_states: Vec<(WalletId, u32, Vec
)> = Vec::new(); + let mut wallet_states: Vec<(WalletId, u32, Vec)> = Vec::new(); for wallet_id in &behind { let synced = wallet.wallet_synced_height(wallet_id); - let addresses = wallet.monitored_addresses_for(wallet_id); - if !addresses.is_empty() { - wallet_states.push((*wallet_id, synced, addresses)); + let scripts = wallet.monitored_script_pubkeys_for(wallet_id); + if !scripts.is_empty() { + wallet_states.push((*wallet_id, synced, scripts)); } } drop(wallet); @@ -762,11 +762,11 @@ impl = - wallet_states.iter().flat_map(|(_, _, addrs)| addrs.iter().cloned()).collect(); + let union_scripts: Vec = + wallet_states.iter().flat_map(|(_, _, scripts)| scripts.iter().cloned()).collect(); let min_synced = wallet_states.iter().map(|(_, synced, _)| *synced).min().unwrap_or(0); let block_to_wallets = { @@ -776,7 +776,7 @@ impl> = BTreeMap::new(); for key in matches { @@ -788,14 +788,12 @@ impl> = - addresses.iter().map(|a| a.script_pubkey().to_bytes()).collect(); let matched = match filter - .match_any(key.hash(), scripts.iter().map(|v| v.as_slice())) + .match_any(key.hash(), scripts.iter().map(|s| s.as_bytes())) { Ok(matched) => matched, Err(e) => { @@ -1288,8 +1286,8 @@ mod tests { assert!(!attr_70.contains(&wallet_high)); } - /// `rescan_batch` with multiple wallets in `addresses_by_wallet`: - /// each wallet's new addresses are matched independently and the + /// `rescan_batch` with multiple wallets in `scripts_by_wallet`: + /// each wallet's new scripts are matched independently and the /// attribution is correct in the emitted `BlocksNeeded`. #[tokio::test] async fn test_rescan_batch_attributes_per_wallet_addresses() { @@ -1318,11 +1316,11 @@ mod tests { batch.mark_verified(); manager.active_batches.insert(0, batch); - let mut new_addresses: HashMap> = HashMap::new(); - new_addresses.insert(wallet_a, HashSet::from([address_a])); - new_addresses.insert(wallet_b, HashSet::from([address_b])); + let mut new_scripts: HashMap> = HashMap::new(); + new_scripts.insert(wallet_a, HashSet::from([address_a.script_pubkey()])); + new_scripts.insert(wallet_b, HashSet::from([address_b.script_pubkey()])); - let events = manager.rescan_batch(0, &new_addresses).await.unwrap(); + let events = manager.rescan_batch(0, &new_scripts).await.unwrap(); let blocks = events .iter() @@ -1394,14 +1392,17 @@ mod tests { batch.mark_verified(); manager.active_batches.insert(0, batch); - // wallet_high also "discovers" address_low to demonstrate that even - // when a new address would match a low height, the per-wallet + // wallet_high also "discovers" address_low's script to demonstrate + // that even when a new script would match a low height, the per-wallet // synced_height filter prevents emitting it. - let mut new_addresses: HashMap> = HashMap::new(); - new_addresses.insert(wallet_low, HashSet::from([address_low.clone()])); - new_addresses.insert(wallet_high, HashSet::from([address_low.clone(), address_high])); + let mut new_scripts: HashMap> = HashMap::new(); + new_scripts.insert(wallet_low, HashSet::from([address_low.script_pubkey()])); + new_scripts.insert( + wallet_high, + HashSet::from([address_low.script_pubkey(), address_high.script_pubkey()]), + ); - let events = manager.rescan_batch(0, &new_addresses).await.unwrap(); + let events = manager.rescan_batch(0, &new_scripts).await.unwrap(); let blocks = events .iter() @@ -1894,30 +1895,30 @@ mod tests { } #[tokio::test] - async fn test_batch_collects_addresses() { + async fn test_batch_collects_scripts() { use crate::sync::filters::batch::FiltersBatch; use dashcore::Network; let mut batch = FiltersBatch::new(0, 4999, HashMap::new()); // Initially empty - assert!(batch.take_collected_addresses().is_empty()); + assert!(batch.take_collected_scripts().is_empty()); - // Add addresses using test utility - let addr1 = dashcore::Address::dummy(Network::Testnet, 1); - let addr2 = dashcore::Address::dummy(Network::Testnet, 2); + // Add scripts using test utility + let script1 = dashcore::Address::dummy(Network::Testnet, 1).script_pubkey(); + let script2 = dashcore::Address::dummy(Network::Testnet, 2).script_pubkey(); let wallet_id: WalletId = [7; 32]; - batch.add_addresses_for_wallet(wallet_id, [addr1.clone(), addr2.clone()]); + batch.add_scripts_for_wallet(wallet_id, [script1.clone(), script2.clone()]); - let collected = batch.take_collected_addresses(); + let collected = batch.take_collected_scripts(); let for_wallet = collected.get(&wallet_id).expect("wallet entry"); assert_eq!(for_wallet.len(), 2); - assert!(for_wallet.contains(&addr1)); - assert!(for_wallet.contains(&addr2)); + assert!(for_wallet.contains(&script1)); + assert!(for_wallet.contains(&script2)); // After take, should be empty - assert!(batch.take_collected_addresses().is_empty()); + assert!(batch.take_collected_scripts().is_empty()); } #[tokio::test] diff --git a/dash-spv/src/sync/filters/sync_manager.rs b/dash-spv/src/sync/filters/sync_manager.rs index 43c52cb0c..3e080880a 100644 --- a/dash-spv/src/sync/filters/sync_manager.rs +++ b/dash-spv/src/sync/filters/sync_manager.rs @@ -169,7 +169,7 @@ impl< block_hash, height, wallets, - new_addresses, + new_scripts, .. } => { // Record per-wallet processing so a future scan can give a @@ -190,13 +190,13 @@ impl< ); } - // Collect per-wallet new addresses for deferred rescan at commit time. - for (wallet_id, addrs) in new_addresses { - if addrs.is_empty() { + // Collect per-wallet new scripts for deferred rescan at commit time. + for (wallet_id, scripts) in new_scripts { + if scripts.is_empty() { continue; } if let Some(batch) = self.active_batches.get_mut(&batch_start) { - batch.add_addresses_for_wallet(*wallet_id, addrs.iter().cloned()); + batch.add_scripts_for_wallet(*wallet_id, scripts.iter().cloned()); } } diff --git a/dash-spv/src/sync/mempool/sync_manager.rs b/dash-spv/src/sync/mempool/sync_manager.rs index 49c8b8e62..f818ed42c 100644 --- a/dash-spv/src/sync/mempool/sync_manager.rs +++ b/dash-spv/src/sync/mempool/sync_manager.rs @@ -390,7 +390,7 @@ mod tests { block_hash: dashcore::BlockHash::all_zeros(), height: 1001, wallets: BTreeSet::new(), - new_addresses: BTreeMap::new(), + new_scripts: BTreeMap::new(), confirmed_txids: txids.clone(), }; let events = manager.handle_sync_event(&event, &requests).await.unwrap(); @@ -576,7 +576,7 @@ mod tests { block_hash: dashcore::BlockHash::all_zeros(), height: 1001, wallets: BTreeSet::new(), - new_addresses: BTreeMap::new(), + new_scripts: BTreeMap::new(), confirmed_txids: vec![dashcore::Txid::all_zeros()], }; manager.handle_sync_event(&event, &requests).await.unwrap(); @@ -603,7 +603,7 @@ mod tests { block_hash: dashcore::BlockHash::all_zeros(), height: 1001, wallets: BTreeSet::new(), - new_addresses: BTreeMap::new(), + new_scripts: BTreeMap::new(), confirmed_txids: vec![], }; manager.handle_sync_event(&event, &requests).await.unwrap(); diff --git a/dash-spv/tests/dashd_sync/helpers.rs b/dash-spv/tests/dashd_sync/helpers.rs index 2a9b1ffc1..a3be238f3 100644 --- a/dash-spv/tests/dashd_sync/helpers.rs +++ b/dash-spv/tests/dashd_sync/helpers.rs @@ -159,9 +159,9 @@ pub(super) fn is_progress_event(event: &SyncEvent) -> bool { .. } => true, SyncEvent::BlockProcessed { - new_addresses, + new_scripts, .. - } => new_addresses.values().any(|v| !v.is_empty()), + } => new_scripts.values().any(|v| !v.is_empty()), _ => false, } } diff --git a/key-wallet-manager/src/lib.rs b/key-wallet-manager/src/lib.rs index 6166e485a..3d0df8aad 100644 --- a/key-wallet-manager/src/lib.rs +++ b/key-wallet-manager/src/lib.rs @@ -21,7 +21,7 @@ mod wallet_interface; pub use error::WalletError; pub use events::{DerivedAddress, WalletEvent}; -pub use matching::{check_compact_filters_for_addresses, FilterMatchKey}; +pub use matching::{check_compact_filters_for_script_pubkeys, FilterMatchKey}; pub use wallet_interface::{BlockProcessingResult, MempoolTransactionResult, WalletInterface}; use dashcore::blockdata::transaction::Transaction; diff --git a/key-wallet-manager/src/matching.rs b/key-wallet-manager/src/matching.rs index acc73865a..84a052439 100644 --- a/key-wallet-manager/src/matching.rs +++ b/key-wallet-manager/src/matching.rs @@ -1,6 +1,6 @@ use dashcore::bip158::BlockFilter; use dashcore::prelude::CoreBlockHeight; -use dashcore::{Address, BlockHash}; +use dashcore::{BlockHash, ScriptBuf}; #[cfg(feature = "parallel-filters")] use rayon::prelude::{IntoParallelIterator, ParallelIterator}; use std::collections::{BTreeSet, HashMap}; @@ -26,23 +26,21 @@ impl FilterMatchKey { } } -/// Check compact filters for addresses and return the keys that matched. +/// Check compact filters against a set of scriptPubKeys and return the keys +/// that matched. /// /// Entries with `key.height() <= min_height` are skipped. Pass `0` to test /// every filter in the input. -pub fn check_compact_filters_for_addresses( +pub fn check_compact_filters_for_script_pubkeys( input: &HashMap, - addresses: Vec
, + script_pubkeys: &[ScriptBuf], min_height: CoreBlockHeight, ) -> BTreeSet { - let script_pubkey_bytes: Vec> = - addresses.iter().map(|address| address.script_pubkey().to_bytes()).collect(); - let match_filter = |(key, filter): (&FilterMatchKey, &BlockFilter)| { if key.height() <= min_height { return None; } - match filter.match_any(key.hash(), script_pubkey_bytes.iter().map(|v| v.as_slice())) { + match filter.match_any(key.hash(), script_pubkeys.iter().map(|s| s.as_bytes())) { Ok(true) => Some(key.clone()), Ok(false) => None, Err(e) => { @@ -70,12 +68,16 @@ pub fn check_compact_filters_for_addresses( #[cfg(test)] mod tests { use super::*; - use dashcore::{Block, Transaction}; + use dashcore::{Address, Block, Transaction}; use key_wallet::Network; + fn scripts_for(addresses: &[Address]) -> Vec { + addresses.iter().map(|a| a.script_pubkey()).collect() + } + #[test] fn test_empty_input_returns_empty() { - let result = check_compact_filters_for_addresses(&HashMap::new(), vec![], 0); + let result = check_compact_filters_for_script_pubkeys(&HashMap::new(), &[], 0); assert!(result.is_empty()); } @@ -90,7 +92,7 @@ mod tests { let mut input = HashMap::new(); input.insert(key.clone(), filter); - let output = check_compact_filters_for_addresses(&input, vec![], 0); + let output = check_compact_filters_for_script_pubkeys(&input, &[], 0); assert!(!output.contains(&key)); } @@ -105,7 +107,8 @@ mod tests { let mut input = HashMap::new(); input.insert(key.clone(), filter); - let output = check_compact_filters_for_addresses(&input, vec![address], 0); + let scripts = scripts_for(&[address]); + let output = check_compact_filters_for_script_pubkeys(&input, &scripts, 0); assert!(output.contains(&key)); } @@ -122,7 +125,8 @@ mod tests { let mut input = HashMap::new(); input.insert(key.clone(), filter); - let output = check_compact_filters_for_addresses(&input, vec![address], 0); + let scripts = scripts_for(&[address]); + let output = check_compact_filters_for_script_pubkeys(&input, &scripts, 0); assert!(!output.contains(&key)); } @@ -152,7 +156,8 @@ mod tests { input.insert(key_2.clone(), filter_2); input.insert(key_3.clone(), filter_3); - let output = check_compact_filters_for_addresses(&input, vec![address_1, address_2], 0); + let scripts = scripts_for(&[address_1, address_2]); + let output = check_compact_filters_for_script_pubkeys(&input, &scripts, 0); assert_eq!(output.len(), 2); assert!(output.contains(&key_1)); assert!(output.contains(&key_2)); @@ -175,7 +180,8 @@ mod tests { input.insert(key, filter); } - let output = check_compact_filters_for_addresses(&input, vec![address], 0); + let scripts = scripts_for(&[address]); + let output = check_compact_filters_for_script_pubkeys(&input, &scripts, 0); // Verify output is sorted by height (ascending) let heights_out: Vec = output.iter().map(|k| k.height()).collect(); diff --git a/key-wallet-manager/src/process_block.rs b/key-wallet-manager/src/process_block.rs index 043b94021..27ea4cba5 100644 --- a/key-wallet-manager/src/process_block.rs +++ b/key-wallet-manager/src/process_block.rs @@ -6,7 +6,7 @@ use core::fmt::Write as _; use dashcore::ephemerealdata::chain_lock::ChainLock; use dashcore::ephemerealdata::instant_lock::InstantLock; use dashcore::prelude::CoreBlockHeight; -use dashcore::{Address, Block, Transaction}; +use dashcore::{Address, Block, ScriptBuf, Transaction}; use key_wallet::account::AccountType; use key_wallet::managed_account::transaction_record::TransactionRecord; use key_wallet::transaction_checking::{BlockInfo, DerivedAddressInfo, TransactionContext}; @@ -70,8 +70,9 @@ impl WalletInterface for WalletM } for (wallet_id, derived) in check_result.new_addresses { - let addresses = derived.iter().map(|d| d.info.address.clone()).collect::>(); - result.new_addresses.entry(wallet_id).or_default().extend(addresses); + let scripts = + derived.iter().map(|d| d.info.script_pubkey.clone()).collect::>(); + result.new_scripts.entry(wallet_id).or_default().extend(scripts); per_wallet_derived.entry(wallet_id).or_default().extend(derived); } for (wallet_id, records) in check_result.per_wallet_new_records { @@ -219,8 +220,11 @@ impl WalletInterface for WalletM self.monitored_addresses() } - fn monitored_addresses_for(&self, wallet_id: &WalletId) -> Vec
{ - self.wallet_infos.get(wallet_id).map(|info| info.monitored_addresses()).unwrap_or_default() + fn monitored_script_pubkeys_for(&self, wallet_id: &WalletId) -> Vec { + self.wallet_infos + .get(wallet_id) + .map(|info| info.monitored_script_pubkeys()) + .unwrap_or_default() } fn watched_outpoints(&self) -> Vec { diff --git a/key-wallet-manager/src/test_utils/mock_wallet.rs b/key-wallet-manager/src/test_utils/mock_wallet.rs index c8917f507..2e9118625 100644 --- a/key-wallet-manager/src/test_utils/mock_wallet.rs +++ b/key-wallet-manager/src/test_utils/mock_wallet.rs @@ -4,7 +4,7 @@ use crate::{ use dashcore::ephemerealdata::chain_lock::ChainLock; use dashcore::ephemerealdata::instant_lock::InstantLock; use dashcore::prelude::CoreBlockHeight; -use dashcore::{Address, Block, OutPoint, Transaction, Txid}; +use dashcore::{Address, Block, OutPoint, ScriptBuf, Transaction, Txid}; use key_wallet::transaction_checking::TransactionContext; use std::collections::BTreeSet; use std::sync::Arc; @@ -143,7 +143,7 @@ impl WalletInterface for MockWallet { BlockProcessingResult { new_txids: block.txdata.iter().map(|tx| tx.txid()).collect(), existing_txids: Vec::new(), - new_addresses: Default::default(), + new_scripts: Default::default(), } } @@ -180,9 +180,9 @@ impl WalletInterface for MockWallet { self.addresses.clone() } - fn monitored_addresses_for(&self, wallet_id: &WalletId) -> Vec
{ + fn monitored_script_pubkeys_for(&self, wallet_id: &WalletId) -> Vec { if wallet_id == &self.wallet_id { - self.addresses.clone() + self.addresses.iter().map(|a| a.script_pubkey()).collect() } else { Vec::new() } @@ -311,7 +311,7 @@ impl WalletInterface for NonMatchingMockWallet { Vec::new() } - fn monitored_addresses_for(&self, _wallet_id: &WalletId) -> Vec
{ + fn monitored_script_pubkeys_for(&self, _wallet_id: &WalletId) -> Vec { Vec::new() } @@ -453,8 +453,11 @@ impl WalletInterface for MultiMockWallet { self.wallets.values().flat_map(|s| s.addresses.iter().cloned()).collect() } - fn monitored_addresses_for(&self, wallet_id: &WalletId) -> Vec
{ - self.wallets.get(wallet_id).map(|s| s.addresses.clone()).unwrap_or_default() + fn monitored_script_pubkeys_for(&self, wallet_id: &WalletId) -> Vec { + self.wallets + .get(wallet_id) + .map(|s| s.addresses.iter().map(|a| a.script_pubkey()).collect()) + .unwrap_or_default() } fn watched_outpoints(&self) -> Vec { diff --git a/key-wallet-manager/src/wallet_interface.rs b/key-wallet-manager/src/wallet_interface.rs index 9b2b5fde0..4974f8a75 100644 --- a/key-wallet-manager/src/wallet_interface.rs +++ b/key-wallet-manager/src/wallet_interface.rs @@ -7,7 +7,7 @@ use async_trait::async_trait; use dashcore::ephemerealdata::chain_lock::ChainLock; use dashcore::ephemerealdata::instant_lock::InstantLock; use dashcore::prelude::CoreBlockHeight; -use dashcore::{Address, Block, OutPoint, Transaction, Txid}; +use dashcore::{Address, Block, OutPoint, ScriptBuf, Transaction, Txid}; use std::collections::{BTreeMap, BTreeSet}; use tokio::sync::broadcast; @@ -18,8 +18,9 @@ pub struct BlockProcessingResult { pub new_txids: Vec, /// Transaction IDs that were already in wallet history pub existing_txids: Vec, - /// New addresses generated per wallet during gap-limit maintenance. - pub new_addresses: BTreeMap>, + /// Cached scriptPubKeys of addresses freshly generated per wallet during + /// gap-limit maintenance. + pub new_scripts: BTreeMap>, } /// Result of processing a mempool transaction through the wallet @@ -48,9 +49,10 @@ impl BlockProcessingResult { self.new_txids.len() + self.existing_txids.len() } - /// Iterate over every newly generated address regardless of wallet attribution. - pub fn all_new_addresses(&self) -> impl Iterator { - self.new_addresses.values().flatten() + /// Iterate over every newly generated scriptPubKey regardless of wallet + /// attribution. + pub fn all_new_scripts(&self) -> impl Iterator { + self.new_scripts.values().flatten() } } @@ -82,8 +84,8 @@ pub trait WalletInterface: Send + Sync + 'static { /// Get all addresses the wallet is monitoring for incoming transactions fn monitored_addresses(&self) -> Vec
; - /// Get monitored addresses for a specific wallet. - fn monitored_addresses_for(&self, wallet_id: &WalletId) -> Vec
; + /// Get cached scriptPubKeys for every address monitored by `wallet_id`. + fn monitored_script_pubkeys_for(&self, wallet_id: &WalletId) -> Vec; /// Get all outpoints the wallet is watching (unspent outputs). /// Used for bloom filter construction to detect spends of our UTXOs. diff --git a/key-wallet-manager/tests/spv_integration_tests.rs b/key-wallet-manager/tests/spv_integration_tests.rs index d30cb12c0..fe26aa8a9 100644 --- a/key-wallet-manager/tests/spv_integration_tests.rs +++ b/key-wallet-manager/tests/spv_integration_tests.rs @@ -3,7 +3,7 @@ use dashcore::blockdata::block::Block; use dashcore::blockdata::transaction::Transaction; use dashcore::constants::COINBASE_MATURITY; -use dashcore::Address; +use dashcore::{Address, ScriptBuf}; use key_wallet::wallet::initialization::WalletAccountCreationOptions; use key_wallet::wallet::managed_wallet_info::wallet_info_interface::WalletInfoInterface; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; @@ -47,15 +47,16 @@ async fn test_block_processing() { assert!(!result.new_txids.contains(&tx3.txid())); // No existing transactions during initial processing assert!(result.existing_txids.is_empty()); - let new_addresses: Vec<_> = result.all_new_addresses().cloned().collect(); - assert_eq!(new_addresses.len(), 2); + let new_scripts: Vec<_> = result.all_new_scripts().cloned().collect(); + assert_eq!(new_scripts.len(), 2); let addresses_after = manager.monitored_addresses(); let actual_increase = addresses_after.len() - addresses_before.len(); - assert_eq!(new_addresses.len(), actual_increase); + assert_eq!(new_scripts.len(), actual_increase); - for new_addr in &new_addresses { - assert!(addresses_after.contains(new_addr)); + let scripts_after: Vec = addresses_after.iter().map(|a| a.script_pubkey()).collect(); + for new_script in &new_scripts { + assert!(scripts_after.contains(new_script)); } } @@ -75,7 +76,7 @@ async fn test_block_processing_result_empty() { assert!(result.new_txids.is_empty()); assert!(result.existing_txids.is_empty()); - assert!(result.new_addresses.is_empty()); + assert!(result.new_scripts.is_empty()); } fn assert_wallet_heights(manager: &WalletManager, expected_height: u32) { diff --git a/key-wallet/src/managed_account/address_pool.rs b/key-wallet/src/managed_account/address_pool.rs index ab9a127f6..a6059667d 100644 --- a/key-wallet/src/managed_account/address_pool.rs +++ b/key-wallet/src/managed_account/address_pool.rs @@ -782,6 +782,11 @@ impl AddressPool { self.addresses.values().map(|info| info.address.clone()).collect() } + /// Get cached scriptPubKeys for every address in the pool. + pub fn all_script_pubkeys(&self) -> Vec { + self.addresses.values().map(|info| info.script_pubkey.clone()).collect() + } + /// Get only used addresses pub fn used_addresses(&self) -> Vec
{ self.addresses.values().filter(|info| info.used).map(|info| info.address.clone()).collect() diff --git a/key-wallet/src/managed_account/managed_account_ref.rs b/key-wallet/src/managed_account/managed_account_ref.rs index 22c495990..e95e36a7a 100644 --- a/key-wallet/src/managed_account/managed_account_ref.rs +++ b/key-wallet/src/managed_account/managed_account_ref.rs @@ -154,6 +154,15 @@ impl<'a> ManagedAccountRef<'a> { ManagedAccountRef::Keys(a) => a.all_addresses(), } } + + /// Return cached scriptPubKey bytes for every address tracked by this + /// account, across all pools. + pub fn all_script_pubkeys(self) -> Vec { + match self { + ManagedAccountRef::Funds(a) => a.all_script_pubkeys(), + ManagedAccountRef::Keys(a) => a.all_script_pubkeys(), + } + } } impl<'a> ManagedAccountRefMut<'a> { diff --git a/key-wallet/src/managed_account/managed_account_trait.rs b/key-wallet/src/managed_account/managed_account_trait.rs index bab178060..dfbb46b28 100644 --- a/key-wallet/src/managed_account/managed_account_trait.rs +++ b/key-wallet/src/managed_account/managed_account_trait.rs @@ -203,6 +203,11 @@ pub trait ManagedAccountTrait { self.managed_account_type().all_addresses() } + /// Get cached scriptPubKey bytes for every address across all pools. + fn all_script_pubkeys(&self) -> Vec { + self.managed_account_type().all_script_pubkeys() + } + /// Check if an address belongs to this account fn contains_address(&self, address: &Address) -> bool { self.managed_account_type().contains_address(address) diff --git a/key-wallet/src/managed_account/managed_account_type.rs b/key-wallet/src/managed_account/managed_account_type.rs index d86c6812f..d89e12fd2 100644 --- a/key-wallet/src/managed_account/managed_account_type.rs +++ b/key-wallet/src/managed_account/managed_account_type.rs @@ -394,6 +394,11 @@ impl ManagedAccountType { self.address_pools().iter().flat_map(|pool| pool.all_addresses()).collect() } + /// Get cached scriptPubKey bytes for every address across all pools. + pub fn all_script_pubkeys(&self) -> Vec { + self.address_pools().iter().flat_map(|pool| pool.all_script_pubkeys()).collect() + } + /// Get the account type as the original enum pub fn to_account_type(&self) -> AccountType { match self { diff --git a/key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs b/key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs index 12f6bb934..da54ea329 100644 --- a/key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs +++ b/key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs @@ -16,7 +16,7 @@ use crate::{Network, Utxo, Wallet, WalletCoreBalance}; use dashcore::ephemerealdata::chain_lock::ChainLock; use dashcore::ephemerealdata::instant_lock::InstantLock; use dashcore::prelude::CoreBlockHeight; -use dashcore::{Address as DashAddress, Transaction, Txid}; +use dashcore::{Address as DashAddress, ScriptBuf, Transaction, Txid}; /// Outcome of [`WalletInfoInterface::apply_chain_lock`]. /// @@ -83,6 +83,9 @@ pub trait WalletInfoInterface: Sized + WalletTransactionChecker + ManagedAccount /// Get all monitored addresses fn monitored_addresses(&self) -> Vec; + /// Get cached scriptPubKeys for every monitored address. + fn monitored_script_pubkeys(&self) -> Vec; + /// Get all UTXOs for the wallet fn utxos(&self) -> BTreeSet<&Utxo>; @@ -304,6 +307,14 @@ impl WalletInfoInterface for ManagedWalletInfo { addresses } + fn monitored_script_pubkeys(&self) -> Vec { + let mut scripts = Vec::new(); + for account in self.accounts.all_accounts() { + scripts.extend(account.all_script_pubkeys()); + } + scripts + } + fn utxos(&self) -> BTreeSet<&Utxo> { let mut utxos = BTreeSet::new(); for account in self.accounts.all_funding_accounts() {