@@ -79,15 +79,17 @@ impl<H: BlockHeaderStorage, B: BlockStorage, W: WalletInterface> BlocksManager<H
7979 let mut events = Vec :: new ( ) ;
8080
8181 // Process blocks in height order using pipeline's ordering logic
82- while let Some ( ( block, height) ) = self . pipeline . take_next_ordered_block ( ) {
82+ while let Some ( ( block, height, interested ) ) = self . pipeline . take_next_ordered_block ( ) {
8383 let hash = block. block_hash ( ) ;
8484
85- // Process block through wallet
85+ // Process the block only for the wallets whose filter matched it.
86+ // Already-synced wallets that did not match are not touched.
8687 let mut wallet = self . wallet . write ( ) . await ;
87- let result = wallet. process_block ( & block, height) . await ;
88+ let result = wallet. process_block_for_wallets ( & block, height, & interested ) . await ;
8889 drop ( wallet) ;
8990
9091 let total_relevant = result. relevant_tx_count ( ) ;
92+ let new_addresses_total: usize = result. new_addresses . values ( ) . map ( |v| v. len ( ) ) . sum ( ) ;
9193 if total_relevant > 0 {
9294 tracing:: info!(
9395 "Found {} relevant transactions ({} new, {} existing) {} at height {}, new addresses: {}" ,
@@ -96,19 +98,20 @@ impl<H: BlockHeaderStorage, B: BlockStorage, W: WalletInterface> BlocksManager<H
9698 result. existing_txids. len( ) ,
9799 hash,
98100 height,
99- result . new_addresses . len ( )
101+ new_addresses_total
100102 ) ;
101103 }
102104
103105 // Collect confirmed txids before moving new_addresses out of result
104106 let confirmed_txids: Vec < _ > = result. relevant_txids ( ) . cloned ( ) . collect ( ) ;
105107
106108 // Collect new addresses for gap limit rescanning
107- let new_addresses: Vec < _ > = result. new_addresses . into_iter ( ) . collect ( ) ;
108- if !new_addresses . is_empty ( ) {
109+ let new_addresses = result. new_addresses ;
110+ if new_addresses_total > 0 {
109111 tracing:: debug!(
110- "Block {} generated {} new addresses for gap limit maintenance" ,
112+ "Block {} generated {} new addresses for gap limit maintenance across {} wallets " ,
111113 height,
114+ new_addresses_total,
112115 new_addresses. len( )
113116 ) ;
114117 }
@@ -124,6 +127,7 @@ impl<H: BlockHeaderStorage, B: BlockStorage, W: WalletInterface> BlocksManager<H
124127 events. push ( SyncEvent :: BlockProcessed {
125128 block_hash : hash,
126129 height,
130+ wallets : interested,
127131 new_addresses,
128132 confirmed_txids,
129133 } ) ;
@@ -168,9 +172,9 @@ mod tests {
168172 } ;
169173 use crate :: sync:: { ManagerIdentifier , SyncEvent , SyncManagerProgress } ;
170174 use crate :: test_utils:: MockNetworkManager ;
171- use key_wallet_manager:: test_utils:: MockWallet ;
175+ use key_wallet_manager:: test_utils:: { MockWallet , MOCK_WALLET_ID } ;
172176 use key_wallet_manager:: FilterMatchKey ;
173- use std:: collections:: BTreeSet ;
177+ use std:: collections:: { BTreeMap , BTreeSet } ;
174178
175179 type TestBlocksManager =
176180 BlocksManager < PersistentBlockHeaderStorage , PersistentBlockStorage , MockWallet > ;
@@ -215,8 +219,8 @@ mod tests {
215219 let requests = network. request_sender ( ) ;
216220
217221 let block_hash = dashcore:: BlockHash :: dummy ( 0 ) ;
218- let mut blocks = BTreeSet :: new ( ) ;
219- blocks. insert ( FilterMatchKey :: new ( 100 , block_hash) ) ;
222+ let mut blocks = BTreeMap :: new ( ) ;
223+ blocks. insert ( FilterMatchKey :: new ( 100 , block_hash) , BTreeSet :: from ( [ MOCK_WALLET_ID ] ) ) ;
220224 let event = SyncEvent :: BlocksNeeded {
221225 blocks,
222226 } ;
@@ -227,4 +231,100 @@ mod tests {
227231 assert_eq ! ( manager. state( ) , SyncState :: Syncing ) ;
228232 assert ! ( events. is_empty( ) ) ;
229233 }
234+
235+ /// `process_buffered_blocks` must call `process_block_for_wallets` with
236+ /// the exact wallet set carried in the pipeline so already-synced
237+ /// wallets are not touched by routing logic.
238+ #[ tokio:: test]
239+ async fn test_process_buffered_blocks_routes_wallet_set ( ) {
240+ use dashcore:: block:: Header ;
241+ use dashcore:: { Block , TxMerkleNode } ;
242+ use dashcore_hashes:: Hash ;
243+
244+ let mut manager = create_test_manager ( ) . await ;
245+ manager. progress . set_state ( SyncState :: Syncing ) ;
246+
247+ let header = Header {
248+ version : dashcore:: blockdata:: block:: Version :: from_consensus ( 1 ) ,
249+ prev_blockhash : dashcore:: BlockHash :: all_zeros ( ) ,
250+ merkle_root : TxMerkleNode :: all_zeros ( ) ,
251+ time : 0 ,
252+ bits : dashcore:: CompactTarget :: from_consensus ( 0 ) ,
253+ nonce : 0 ,
254+ } ;
255+ let block = Block {
256+ header,
257+ txdata : vec ! [ ] ,
258+ } ;
259+ manager. pipeline . add_from_storage ( block. clone ( ) , 100 , BTreeSet :: from ( [ MOCK_WALLET_ID ] ) ) ;
260+
261+ let events = manager. process_buffered_blocks ( ) . await . unwrap ( ) ;
262+ assert ! ( matches!( events. first( ) , Some ( SyncEvent :: BlockProcessed { .. } ) ) ) ;
263+
264+ // MOCK_WALLET_ID was in the routed set, so MockWallet recorded the
265+ // block. (MockWallet::process_block_for_wallets returns early when
266+ // its id is absent.)
267+ let processed = manager. wallet . read ( ) . await . processed_blocks ( ) ;
268+ let processed = processed. lock ( ) . await ;
269+ assert_eq ! ( processed. len( ) , 1 ) ;
270+ assert_eq ! ( processed[ 0 ] . 1 , 100 ) ;
271+ }
272+
273+ /// A wallet that is NOT in the pipeline's interested set must not be
274+ /// routed the block. Two wallets are registered, but only `wallet_in`
275+ /// appears in the routed set; the other wallet's processed log must
276+ /// stay empty for that block.
277+ #[ tokio:: test]
278+ async fn test_process_buffered_blocks_excludes_uninterested_wallet ( ) {
279+ use dashcore:: block:: Header ;
280+ use dashcore:: { Block , TxMerkleNode } ;
281+ use dashcore_hashes:: Hash ;
282+ use key_wallet_manager:: test_utils:: { MockWalletState , MultiMockWallet } ;
283+ use key_wallet_manager:: WalletId ;
284+
285+ let storage = DiskStorageManager :: with_temp_dir ( ) . await . unwrap ( ) ;
286+ let multi = MultiMockWallet :: new ( ) ;
287+ let wallet_in: WalletId = [ 0xAA ; 32 ] ;
288+ let wallet_out: WalletId = [ 0xBB ; 32 ] ;
289+ let multi = Arc :: new ( RwLock :: new ( multi) ) ;
290+ {
291+ let mut w = multi. write ( ) . await ;
292+ w. insert_wallet ( wallet_in, MockWalletState :: default ( ) ) ;
293+ w. insert_wallet ( wallet_out, MockWalletState :: default ( ) ) ;
294+ }
295+ let mut manager: BlocksManager <
296+ PersistentBlockHeaderStorage ,
297+ PersistentBlockStorage ,
298+ MultiMockWallet ,
299+ > = BlocksManager :: new ( multi. clone ( ) , storage. block_headers ( ) , storage. blocks ( ) ) . await ;
300+ manager. progress . set_state ( SyncState :: Syncing ) ;
301+
302+ let header = Header {
303+ version : dashcore:: blockdata:: block:: Version :: from_consensus ( 1 ) ,
304+ prev_blockhash : dashcore:: BlockHash :: all_zeros ( ) ,
305+ merkle_root : TxMerkleNode :: all_zeros ( ) ,
306+ time : 0 ,
307+ bits : dashcore:: CompactTarget :: from_consensus ( 0 ) ,
308+ nonce : 0 ,
309+ } ;
310+ let block = Block {
311+ header,
312+ txdata : vec ! [ ] ,
313+ } ;
314+ // Only wallet_in is in the routed set.
315+ manager. pipeline . add_from_storage ( block. clone ( ) , 100 , BTreeSet :: from ( [ wallet_in] ) ) ;
316+
317+ let _ = manager. process_buffered_blocks ( ) . await . unwrap ( ) ;
318+
319+ let processed = multi. read ( ) . await . processed ( ) ;
320+ let processed = processed. lock ( ) . await ;
321+ // Exactly one entry, for wallet_in only.
322+ assert_eq ! ( processed. len( ) , 1 ) ;
323+ assert_eq ! ( processed[ 0 ] . 0 , wallet_in) ;
324+ assert_eq ! ( processed[ 0 ] . 2 , 100 ) ;
325+ assert ! (
326+ !processed. iter( ) . any( |( id, _, _) | * id == wallet_out) ,
327+ "wallet_out was not in the routed set, must not be processed"
328+ ) ;
329+ }
230330}
0 commit comments