diff --git a/dash-spv/benches/storage.rs b/dash-spv/benches/storage.rs index 44452c303..8466d6b76 100644 --- a/dash-spv/benches/storage.rs +++ b/dash-spv/benches/storage.rs @@ -28,7 +28,10 @@ fn bench_disk_storage(c: &mut Criterion) { let rt = Builder::new_multi_thread().worker_threads(4).enable_all().build().unwrap(); - let headers = (0..NUM_ELEMENTS).map(create_test_header).collect::>(); + let headers = (0..NUM_ELEMENTS) + .map(create_test_header) + .map(dash_spv::types::HashedBlockHeader::from) + .collect::>(); let mut rng = StdRng::seed_from_u64(SEED); c.bench_function("storage/disk/store", |b| { @@ -75,7 +78,7 @@ fn bench_disk_storage(c: &mut Criterion) { b.to_async(&rt).iter_batched( || { let height = rand::random::() % NUM_ELEMENTS; - headers[height as usize].block_hash() + *headers[height as usize].hash() }, async |hash| { let _ = storage.get_header_height_by_hash(&hash).await.unwrap(); diff --git a/dash-spv/src/client/lifecycle.rs b/dash-spv/src/client/lifecycle.rs index 8c7d4f29f..d844ceec7 100644 --- a/dash-spv/src/client/lifecycle.rs +++ b/dash-spv/src/client/lifecycle.rs @@ -291,7 +291,10 @@ impl DashSpvClient DashSpvClient StorageResult<()>; + async fn store_headers(&mut self, headers: &[HashedBlockHeader]) -> StorageResult<()>; async fn store_headers_at_height( - &mut self, - headers: &[BlockHeader], - height: u32, - ) -> StorageResult<()>; - - //TODO - change API of the BlockHeaderStorage trait to accept (store) and return (load) - // HashedBlockHeaders instead of BlockHeaders to avoid unnecessary hashing and remove - // the two store_hashed_headers methods below. - async fn store_hashed_headers(&mut self, headers: &[HashedBlockHeader]) -> StorageResult<()>; - - async fn store_hashed_headers_at_height( &mut self, headers: &[HashedBlockHeader], height: u32, @@ -66,9 +55,9 @@ pub trait BlockHeaderStorage: Send + Sync + 'static { /// Returns `StorageError::InvalidArgument` when the range extends into a /// segment queued for deletion by a prior `truncate_above` (before the next /// `persist`). Callers must clamp the range to at most `get_tip_height`. - async fn load_headers(&self, range: Range) -> StorageResult>; + async fn load_headers(&self, range: Range) -> StorageResult>; - async fn get_header(&self, height: u32) -> StorageResult> { + async fn get_header(&self, height: u32) -> StorageResult> { if let Some(tip_height) = self.get_tip_height().await { if height > tip_height { return Ok(None); @@ -85,7 +74,7 @@ pub trait BlockHeaderStorage: Send + Sync + 'static { return Ok(None); } - Ok(self.load_headers(height..height + 1).await?.first().copied()) + Ok(self.load_headers(height..height + 1).await?.into_iter().next()) } async fn get_tip_height(&self) -> Option; @@ -161,27 +150,12 @@ impl PersistentStorage for PersistentBlockHeaderStorage { #[async_trait] impl BlockHeaderStorage for PersistentBlockHeaderStorage { - async fn store_headers(&mut self, headers: &[BlockHeader]) -> StorageResult<()> { + async fn store_headers(&mut self, headers: &[HashedBlockHeader]) -> StorageResult<()> { let height = self.block_headers.read().await.next_height(); self.store_headers_at_height(headers, height).await } async fn store_headers_at_height( - &mut self, - headers: &[BlockHeader], - height: u32, - ) -> StorageResult<()> { - let headers = - headers.iter().map(HashedBlockHeader::from).collect::>(); - self.store_hashed_headers_at_height(&headers, height).await - } - - async fn store_hashed_headers(&mut self, headers: &[HashedBlockHeader]) -> StorageResult<()> { - let height = self.block_headers.read().await.next_height(); - self.store_hashed_headers_at_height(headers, height).await - } - - async fn store_hashed_headers_at_height( &mut self, headers: &[HashedBlockHeader], height: u32, @@ -198,16 +172,9 @@ impl BlockHeaderStorage for PersistentBlockHeaderStorage { Ok(()) } - async fn load_headers(&self, range: Range) -> StorageResult> { - Ok(self - .block_headers - .write() - .await - .get_items(range) - .await? - .into_iter() - .map(|cached| *cached.header()) - .collect()) + // Returns the cached `HashedBlockHeader`s straight from the segment cache — no recomputation. + async fn load_headers(&self, range: Range) -> StorageResult> { + self.block_headers.write().await.get_items(range).await } async fn get_tip_height(&self) -> Option { @@ -268,9 +235,13 @@ mod tests { use super::*; use tempfile::TempDir; + fn hashed_batch(r: std::ops::Range) -> Vec { + BlockHeader::dummy_batch(r).into_iter().map(HashedBlockHeader::from).collect() + } + #[tokio::test] async fn test_get_tip() { - let headers = BlockHeader::dummy_batch(0..5); + let headers = hashed_batch(0..5); let tmp_dir = TempDir::new().unwrap(); let mut storage = PersistentBlockHeaderStorage::open(tmp_dir.path()).await.unwrap(); // Tip should be none before storing headers @@ -278,13 +249,13 @@ mod tests { // Add one header and validate tip storage.store_headers(&headers[0..1]).await.unwrap(); let tip = storage.get_tip().await.unwrap(); - let expected_tip = BlockHeaderTip::new(0, HashedBlockHeader::from(headers[0])); + let expected_tip = BlockHeaderTip::new(0, headers[0].clone()); assert_eq!(tip, expected_tip); assert_eq!(storage.get_tip_height().await, Some(0)); // Add multiple headers and validate tip storage.store_headers(&headers[1..]).await.unwrap(); let tip = storage.get_tip().await.unwrap(); - let expected_tip = BlockHeaderTip::new(4, HashedBlockHeader::from(headers[4])); + let expected_tip = BlockHeaderTip::new(4, headers[4].clone()); assert_eq!(tip, expected_tip); assert_eq!(storage.get_tip_height().await, Some(4)); } @@ -294,35 +265,32 @@ mod tests { let tmp_dir = TempDir::new().unwrap(); let mut storage = PersistentBlockHeaderStorage::open(tmp_dir.path()).await.unwrap(); - let headers = BlockHeader::dummy_batch(0..10); + let headers = hashed_batch(0..10); storage.store_headers(&headers).await.unwrap(); - let orphaned_hash = headers[7].block_hash(); + let orphaned_hash = *headers[7].hash(); assert_eq!(storage.get_header_height_by_hash(&orphaned_hash).await.unwrap(), Some(7)); storage.truncate_above(100).await.unwrap(); assert_eq!(storage.get_tip_height().await, Some(9)); - assert_eq!( - storage.get_header_height_by_hash(&headers[4].block_hash()).await.unwrap(), - Some(4) - ); + assert_eq!(storage.get_header_height_by_hash(headers[4].hash()).await.unwrap(), Some(4)); storage.truncate_above(5).await.unwrap(); assert_eq!(storage.get_tip_height().await, Some(5)); assert_eq!(storage.get_header_height_by_hash(&orphaned_hash).await.unwrap(), None); - let kept_hash = headers[3].block_hash(); + let kept_hash = *headers[3].hash(); assert_eq!(storage.get_header_height_by_hash(&kept_hash).await.unwrap(), Some(3)); - let replacement = BlockHeader::dummy_batch(100..105); + let replacement = hashed_batch(100..105); storage.store_headers_at_height(&replacement, 6).await.unwrap(); assert_eq!(storage.get_tip_height().await, Some(10)); let reloaded = storage.load_headers(6..11).await.unwrap(); assert_eq!(reloaded, replacement); - let new_hash = replacement[0].block_hash(); + let new_hash = *replacement[0].hash(); assert_eq!(storage.get_header_height_by_hash(&new_hash).await.unwrap(), Some(6)); // Exercise the durability contract: persist, drop, reopen, and verify diff --git a/dash-spv/src/storage/mod.rs b/dash-spv/src/storage/mod.rs index 57e4fb736..453b60037 100644 --- a/dash-spv/src/storage/mod.rs +++ b/dash-spv/src/storage/mod.rs @@ -19,7 +19,6 @@ use crate::ClientConfig; use async_trait::async_trait; use dashcore::hash_types::FilterHeader; use dashcore::prelude::CoreBlockHeight; -use dashcore::Header as BlockHeader; use std::ops::Range; use std::path::PathBuf; use std::sync::Arc; @@ -269,31 +268,19 @@ impl StorageManager for DiskStorageManager { #[async_trait] impl BlockHeaderStorage for DiskStorageManager { - async fn store_headers(&mut self, headers: &[BlockHeader]) -> StorageResult<()> { + async fn store_headers(&mut self, headers: &[HashedBlockHeader]) -> StorageResult<()> { self.block_headers.write().await.store_headers(headers).await } async fn store_headers_at_height( - &mut self, - headers: &[BlockHeader], - height: u32, - ) -> StorageResult<()> { - self.block_headers.write().await.store_headers_at_height(headers, height).await - } - - async fn store_hashed_headers(&mut self, headers: &[HashedBlockHeader]) -> StorageResult<()> { - self.block_headers.write().await.store_hashed_headers(headers).await - } - - async fn store_hashed_headers_at_height( &mut self, headers: &[HashedBlockHeader], height: u32, ) -> StorageResult<()> { - self.block_headers.write().await.store_hashed_headers_at_height(headers, height).await + self.block_headers.write().await.store_headers_at_height(headers, height).await } - async fn load_headers(&self, range: Range) -> StorageResult> { + async fn load_headers(&self, range: Range) -> StorageResult> { self.block_headers.read().await.load_headers(range).await } @@ -426,6 +413,10 @@ mod tests { use dashcore::Header as BlockHeader; use tempfile::TempDir; + fn hashed_batch(r: std::ops::Range) -> Vec { + BlockHeader::dummy_batch(r).into_iter().map(HashedBlockHeader::from).collect() + } + #[tokio::test] async fn test_store_load_headers() -> Result<(), Box> { // Create a temporary directory for the test @@ -433,7 +424,7 @@ mod tests { let config = ClientConfig::testnet().with_storage_path(temp_dir.path()); let mut storage = DiskStorageManager::new(&config).await.expect("Unable to create storage"); - let headers = BlockHeader::dummy_batch(0..60_000); + let headers = hashed_batch(0..60_000); storage.store_headers(&headers[0..0]).await.expect("Should handle empty header batch"); assert_eq!(storage.get_tip_height().await, None); @@ -478,8 +469,8 @@ mod tests { // Create test headers starting from checkpoint height const CHECKPOINT_HEIGHT: u32 = 1_100_000; - let headers: Vec = - BlockHeader::dummy_batch(CHECKPOINT_HEIGHT..CHECKPOINT_HEIGHT + 100); + let headers: Vec = + hashed_batch(CHECKPOINT_HEIGHT..CHECKPOINT_HEIGHT + 100); storage.store_headers_at_height(&headers, CHECKPOINT_HEIGHT).await?; @@ -496,18 +487,18 @@ mod tests { async fn check_storage( storage: &DiskStorageManager, - headers: &[BlockHeader], + headers: &[HashedBlockHeader], ) -> StorageResult<()> { assert_eq!(storage.get_stored_headers_len().await, headers.len() as u32); let header_at_base = storage.get_header(CHECKPOINT_HEIGHT).await?; - assert_eq!(header_at_base, Some(headers[0])); + assert_eq!(header_at_base, Some(headers[0].clone())); let header_at_ending = storage.get_header(CHECKPOINT_HEIGHT + 99).await?; - assert_eq!(header_at_ending, Some(headers[99])); + assert_eq!(header_at_ending, Some(headers[99].clone())); // Test the reverse index (hash -> blockchain height) - let hash_0 = headers[0].block_hash(); + let hash_0 = *headers[0].hash(); let height_0 = storage.get_header_height_by_hash(&hash_0).await?; assert_eq!( height_0, @@ -515,7 +506,7 @@ mod tests { "Hash should map to blockchain height 1,100,000" ); - let hash_99 = headers[99].block_hash(); + let hash_99 = *headers[99].hash(); let height_99 = storage.get_header_height_by_hash(&hash_99).await?; assert_eq!( height_99, @@ -536,13 +527,13 @@ mod tests { let mut storage = DiskStorageManager::new(&config).await.unwrap(); // Create and store headers - let headers = BlockHeader::dummy_batch(0..10); + let headers = hashed_batch(0..10); storage.store_headers(&headers).await.unwrap(); // Test reverse lookups for (i, header) in headers.iter().enumerate() { - let hash = header.block_hash(); + let hash = *header.hash(); let height = storage.get_header_height_by_hash(&hash).await.unwrap(); assert_eq!(height, Some(i as u32), "Height mismatch for header {}", i); } @@ -558,7 +549,7 @@ mod tests { // We need to get the actual headers that were stored to test properly for i in 0..10 { let stored_header = storage.get_header(i).await.unwrap().unwrap(); - let hash = stored_header.block_hash(); + let hash = *stored_header.hash(); let height = storage.get_header_height_by_hash(&hash).await.unwrap(); assert_eq!(height, Some(i), "Height mismatch after reload for header {}", i); } @@ -571,10 +562,10 @@ mod tests { DiskStorageManager::with_temp_dir().await.expect("Failed to create tmp storage"); // Store some headers - let header = BlockHeader::dummy_batch(0..1); + let header = hashed_batch(0..1); storage.store_headers(&header).await.unwrap(); - let hash = header[0].block_hash(); + let hash = *header[0].hash(); assert!(storage.get_header_height_by_hash(&hash).await.unwrap().is_some()); // Clear storage @@ -590,10 +581,10 @@ mod tests { let config = ClientConfig::regtest().with_storage_path(temp_dir.path()); let mut mgr = DiskStorageManager::new(&config).await.unwrap(); - let headers = BlockHeader::dummy_batch(0..10); + let headers = hashed_batch(0..10); mgr.store_headers(&headers).await.unwrap(); - let orphaned_hash = headers[7].block_hash(); + let orphaned_hash = *headers[7].hash(); assert_eq!(mgr.get_header_height_by_hash(&orphaned_hash).await.unwrap(), Some(7)); ::truncate_above(&mut mgr, 5).await.unwrap(); @@ -601,7 +592,7 @@ mod tests { assert_eq!(mgr.get_tip_height().await, Some(5)); assert_eq!(mgr.get_header_height_by_hash(&orphaned_hash).await.unwrap(), None); - let kept_hash = headers[3].block_hash(); + let kept_hash = *headers[3].hash(); assert_eq!(mgr.get_header_height_by_hash(&kept_hash).await.unwrap(), Some(3)); } diff --git a/dash-spv/src/sync/block_headers/manager.rs b/dash-spv/src/sync/block_headers/manager.rs index 713699f7c..b3699e979 100644 --- a/dash-spv/src/sync/block_headers/manager.rs +++ b/dash-spv/src/sync/block_headers/manager.rs @@ -108,7 +108,7 @@ impl BlockHeadersManager { BlockHeaderValidator::new().validate(headers)?; // Store headers - self.header_storage.write().await.store_hashed_headers(headers).await?; + self.header_storage.write().await.store_headers(headers).await?; let tip = self.tip().await?; @@ -277,7 +277,12 @@ mod tests { let mut storage = DiskStorageManager::with_temp_dir().await.unwrap(); // Store a genesis header so the manager can initialize let genesis = Header::dummy_batch(0..1); - storage.store_headers(&genesis).await.unwrap(); + storage + .store_headers( + &genesis.iter().map(crate::types::HashedBlockHeader::from).collect::>(), + ) + .await + .unwrap(); let checkpoint_manager = create_test_checkpoint_manager(); BlockHeadersManager::new(storage.block_headers(), storage.metadata(), checkpoint_manager) .await diff --git a/dash-spv/src/sync/blocks/manager.rs b/dash-spv/src/sync/blocks/manager.rs index da9e01220..70df8624b 100644 --- a/dash-spv/src/sync/blocks/manager.rs +++ b/dash-spv/src/sync/blocks/manager.rs @@ -80,12 +80,13 @@ impl BlocksManager, /// Heights queued or in-flight (waiting for download). pending_heights: BTreeSet, - /// Downloaded blocks ready to process (height -> Block). - downloaded: BTreeMap, + /// Downloaded blocks ready to process (height -> block, with its cached hash). + downloaded: BTreeMap, /// Map hash -> height for looking up height when block arrives. hash_to_height: HashMap, /// Per-block interested wallets, populated when the block is queued. @@ -138,8 +138,8 @@ impl BlocksPipeline { /// Looks up the height from the internal hash_to_height map and stores /// the block in the downloaded buffer for height-ordered processing. /// Returns `true` if this was a tracked block, `false` if unrequested. - pub(super) fn receive_block(&mut self, block: &Block) -> bool { - let hash = block.block_hash(); + pub(super) fn receive_block(&mut self, block: &HashedBlock) -> bool { + let hash = *block.hash(); if !self.coordinator.receive(&hash) { tracing::debug!("Ignoring unrequested block: {}", hash); return false; @@ -158,7 +158,9 @@ impl BlocksPipeline { /// Returns None if: /// - No downloaded blocks available, or /// - Waiting for a lower-height block still pending - pub(super) fn take_next_ordered_block(&mut self) -> Option<(Block, u32, BTreeSet)> { + pub(super) fn take_next_ordered_block( + &mut self, + ) -> Option<(HashedBlock, u32, BTreeSet)> { let lowest_downloaded = *self.downloaded.keys().next()?; // Check if any pending blocks have lower heights @@ -169,7 +171,7 @@ impl BlocksPipeline { } let block = self.downloaded.remove(&lowest_downloaded).unwrap(); - let wallets = self.hash_to_wallets.remove(&block.block_hash()).unwrap_or_default(); + let wallets = self.hash_to_wallets.remove(block.hash()).unwrap_or_default(); Some((block, lowest_downloaded, wallets)) } @@ -178,11 +180,11 @@ impl BlocksPipeline { /// Used when blocks are already persisted from a previous sync. pub(super) fn add_from_storage( &mut self, - block: Block, + block: HashedBlock, height: u32, wallets: BTreeSet, ) { - let hash = block.block_hash(); + let hash = *block.hash(); self.hash_to_wallets.entry(hash).or_default().extend(wallets); self.downloaded.insert(height, block); } @@ -204,6 +206,7 @@ impl BlocksPipeline { #[cfg(test)] mod tests { + use dashcore::blockdata::block::Block; use dashcore_hashes::Hash; use super::*; @@ -281,11 +284,11 @@ mod tests { assert_eq!(pipeline.coordinator.active_count(), 1); // Receive block - assert!(pipeline.receive_block(&block)); + assert!(pipeline.receive_block(&HashedBlock::from(&block))); assert_eq!(pipeline.coordinator.active_count(), 0); assert_eq!(pipeline.downloaded.len(), 1); assert!(pipeline.pending_heights.is_empty()); - assert_eq!(pipeline.downloaded.get(&100).unwrap().block_hash(), hash); + assert_eq!(*pipeline.downloaded.get(&100).unwrap().hash(), hash); } #[test] @@ -293,7 +296,7 @@ mod tests { let mut pipeline = BlocksPipeline::new(); let block = make_test_block(1); - assert!(!pipeline.receive_block(&block)); + assert!(!pipeline.receive_block(&HashedBlock::from(&block))); assert!(pipeline.downloaded.is_empty()); } @@ -332,7 +335,7 @@ mod tests { assert_eq!(pipeline.coordinator.active_count(), 1); // B: already received, sitting in `downloaded` — must survive requeue. - pipeline.add_from_storage(block_b.clone(), 200, BTreeSet::from([[2u8; 32]])); + pipeline.add_from_storage(HashedBlock::from(&block_b), 200, BTreeSet::from([[2u8; 32]])); pipeline.requeue_in_flight(); @@ -385,7 +388,7 @@ mod tests { // Use add_from_storage to test ordering logic without network // Add block 2 first (out of order) - pipeline.add_from_storage(block2.clone(), 101, BTreeSet::new()); + pipeline.add_from_storage(HashedBlock::from(&block2), 101, BTreeSet::new()); // Also track height 100 as pending to simulate waiting pipeline.pending_heights.insert(100); @@ -394,17 +397,17 @@ mod tests { // Add block 1 pipeline.pending_heights.remove(&100); - pipeline.add_from_storage(block1.clone(), 100, BTreeSet::new()); + pipeline.add_from_storage(HashedBlock::from(&block1), 100, BTreeSet::new()); // Now block 1 is ready (lowest height) let (block, height, _) = pipeline.take_next_ordered_block().unwrap(); assert_eq!(height, 100); - assert_eq!(block.block_hash(), hash1); + assert_eq!(*block.hash(), hash1); // Block 2 is now ready let (block, height, _) = pipeline.take_next_ordered_block().unwrap(); assert_eq!(height, 101); - assert_eq!(block.block_hash(), hash2); + assert_eq!(*block.hash(), hash2); // No more blocks assert!(pipeline.take_next_ordered_block().is_none()); @@ -417,7 +420,7 @@ mod tests { // Add block at height 101, but height 100 is still pending pipeline.pending_heights.insert(100); - pipeline.add_from_storage(block2.clone(), 101, BTreeSet::new()); + pipeline.add_from_storage(HashedBlock::from(&block2), 101, BTreeSet::new()); // Cannot take block 2 - block at height 100 is still pending assert!(pipeline.take_next_ordered_block().is_none()); @@ -436,13 +439,13 @@ mod tests { let block = make_test_block(1); let hash = block.block_hash(); - pipeline.add_from_storage(block.clone(), 100, BTreeSet::new()); + pipeline.add_from_storage(HashedBlock::from(&block), 100, BTreeSet::new()); assert_eq!(pipeline.downloaded.len(), 1); let (taken_block, height, _) = pipeline.take_next_ordered_block().unwrap(); assert_eq!(height, 100); - assert_eq!(taken_block.block_hash(), hash); + assert_eq!(*taken_block.hash(), hash); } #[test] @@ -452,7 +455,7 @@ mod tests { // Adding to downloaded makes it incomplete let block = make_test_block(1); - pipeline.add_from_storage(block, 100, BTreeSet::new()); + pipeline.add_from_storage(HashedBlock::from(&block), 100, BTreeSet::new()); assert!(!pipeline.is_complete()); // Take the block @@ -487,10 +490,10 @@ mod tests { // Drive the block through receive_block to land it in `downloaded`. let hashes = pipeline.coordinator.take_pending(1); pipeline.coordinator.mark_sent(&hashes); - assert!(pipeline.receive_block(&block)); + assert!(pipeline.receive_block(&HashedBlock::from(&block))); let (taken_block, height, taken_wallets) = pipeline.take_next_ordered_block().unwrap(); - assert_eq!(taken_block.block_hash(), hash); + assert_eq!(*taken_block.hash(), hash); assert_eq!(height, 100); assert_eq!(taken_wallets, wallets); } @@ -516,7 +519,7 @@ mod tests { let hashes = pipeline.coordinator.take_pending(1); assert_eq!(hashes.len(), 1); pipeline.coordinator.mark_sent(&hashes); - assert!(pipeline.receive_block(&block)); + assert!(pipeline.receive_block(&HashedBlock::from(&block))); let (_, _, taken_wallets) = pipeline.take_next_ordered_block().unwrap(); let mut expected = wallets_a; @@ -550,7 +553,7 @@ mod tests { assert_eq!(pipeline.coordinator.active_count(), 1); // Late wallet ids are still merged for when the block arrives. - assert!(pipeline.receive_block(&block)); + assert!(pipeline.receive_block(&HashedBlock::from(&block))); let (_, _, taken_wallets) = pipeline.take_next_ordered_block().unwrap(); let mut expected = wallets_a; expected.extend(wallets_b); @@ -571,7 +574,7 @@ mod tests { pipeline.queue([(FilterMatchKey::new(100, hash), wallets_a.clone())]); let hashes = pipeline.coordinator.take_pending(1); pipeline.coordinator.mark_sent(&hashes); - assert!(pipeline.receive_block(&block)); + assert!(pipeline.receive_block(&HashedBlock::from(&block))); assert_eq!(pipeline.downloaded.len(), 1); assert_eq!(pipeline.coordinator.pending_count(), 0); assert_eq!(pipeline.coordinator.active_count(), 0); @@ -598,8 +601,8 @@ mod tests { let wallets_a: BTreeSet = BTreeSet::from([[1u8; 32]]); let wallets_b: BTreeSet = BTreeSet::from([[2u8; 32]]); - pipeline.add_from_storage(block.clone(), 100, wallets_a.clone()); - pipeline.add_from_storage(block.clone(), 100, wallets_b.clone()); + pipeline.add_from_storage(HashedBlock::from(&block), 100, wallets_a.clone()); + pipeline.add_from_storage(HashedBlock::from(&block), 100, wallets_b.clone()); let (_, _, taken_wallets) = pipeline.take_next_ordered_block().unwrap(); let mut expected = wallets_a; @@ -618,12 +621,12 @@ mod tests { pipeline.coordinator.mark_sent(&hashes); // First receive - let result = pipeline.receive_block(&block); + let result = pipeline.receive_block(&HashedBlock::from(&block)); assert!(result); assert_eq!(pipeline.downloaded.len(), 1); // Duplicate receive (not tracked anymore since already completed) - let result = pipeline.receive_block(&block); + let result = pipeline.receive_block(&HashedBlock::from(&block)); assert!(!result); assert_eq!(pipeline.downloaded.len(), 1); } diff --git a/dash-spv/src/sync/blocks/sync_manager.rs b/dash-spv/src/sync/blocks/sync_manager.rs index 871d78f23..e7ecbc68a 100644 --- a/dash-spv/src/sync/blocks/sync_manager.rs +++ b/dash-spv/src/sync/blocks/sync_manager.rs @@ -74,7 +74,7 @@ impl SyncM let hashed_block = HashedBlock::from(block); // Check if this is a block we requested (pipeline handles buffering with height) - if !self.pipeline.receive_block(block) { + if !self.pipeline.receive_block(&hashed_block) { tracing::debug!("Received unrequested block {}", hashed_block.hash()); return Ok(vec![]); } @@ -146,11 +146,7 @@ impl SyncM ))); } // Block loaded from storage, add to pipeline for processing - self.pipeline.add_from_storage( - hashed_block.block().clone(), - key.height(), - wallets.clone(), - ); + self.pipeline.add_from_storage(hashed_block, key.height(), wallets.clone()); self.progress.add_from_storage(1); continue; } diff --git a/dash-spv/src/sync/chainlock/manager.rs b/dash-spv/src/sync/chainlock/manager.rs index 4b55d840f..c211919df 100644 --- a/dash-spv/src/sync/chainlock/manager.rs +++ b/dash-spv/src/sync/chainlock/manager.rs @@ -227,7 +227,7 @@ impl ChainLockManager { async fn verify_block_hash(&self, chainlock: &ChainLock) -> bool { let storage = self.header_storage.read().await; match storage.get_header(chainlock.block_height).await { - Ok(Some(header)) => header.block_hash() == chainlock.block_hash, + Ok(Some(header)) => *header.hash() == chainlock.block_hash, Ok(None) => { // Don't reject if we don't have the header yet true @@ -412,7 +412,7 @@ mod tests { .block_headers() .write() .await - .store_headers_at_height(&[header], 100) + .store_headers_at_height(&[crate::types::HashedBlockHeader::from(header)], 100) .await .expect("store header at 100"); diff --git a/dash-spv/src/sync/filter_headers/pipeline.rs b/dash-spv/src/sync/filter_headers/pipeline.rs index 0640f4d53..309b28ca0 100644 --- a/dash-spv/src/sync/filter_headers/pipeline.rs +++ b/dash-spv/src/sync/filter_headers/pipeline.rs @@ -87,13 +87,10 @@ impl FilterHeadersPipeline { let batch_end = (current + FILTER_HEADERS_BATCH_SIZE - 1).min(new_target); // Get stop hash for this batch - let stop_hash = storage - .get_header(batch_end) - .await? - .ok_or_else(|| { + let stop_hash = + storage.get_header(batch_end).await?.map(|h| *h.hash()).ok_or_else(|| { SyncError::Storage(format!("Missing header at height {}", batch_end)) - })? - .block_hash(); + })?; self.coordinator.enqueue([stop_hash]); self.batch_starts.insert(stop_hash, current); @@ -145,13 +142,10 @@ impl FilterHeadersPipeline { let batch_end = (current + FILTER_HEADERS_BATCH_SIZE - 1).min(target_height); // Get stop hash for this batch - let stop_hash = storage - .get_header(batch_end) - .await? - .ok_or_else(|| { + let stop_hash = + storage.get_header(batch_end).await?.map(|h| *h.hash()).ok_or_else(|| { SyncError::Storage(format!("Missing header at height {}", batch_end)) - })? - .block_hash(); + })?; self.coordinator.enqueue([stop_hash]); self.batch_starts.insert(stop_hash, current); diff --git a/dash-spv/src/sync/filters/manager.rs b/dash-spv/src/sync/filters/manager.rs index d2e45e9a2..d1e961a71 100644 --- a/dash-spv/src/sync/filters/manager.rs +++ b/dash-spv/src/sync/filters/manager.rs @@ -154,7 +154,7 @@ impl>(), + ) + .await + .unwrap(); // Pre-populate filter header storage with headers at heights 1..=200 let filter_headers = storage.filter_headers(); @@ -1737,7 +1748,15 @@ mod tests { // Headers must exist in storage so start_download can resolve them. let headers = dashcore::block::Header::dummy_batch(0..301); - manager.header_storage.write().await.store_headers(&headers).await.unwrap(); + manager + .header_storage + .write() + .await + .store_headers( + &headers.iter().map(crate::types::HashedBlockHeader::from).collect::>(), + ) + .await + .unwrap(); let (tx, _rx) = unbounded_channel(); let _ = manager.tick(&RequestSender::new(tx)).await.unwrap(); @@ -2002,7 +2021,15 @@ mod tests { // Store headers so send_pending can resolve stop hashes let headers = dashcore::block::Header::dummy_batch(0..101); - manager.header_storage.write().await.store_headers(&headers).await.unwrap(); + manager + .header_storage + .write() + .await + .store_headers( + &headers.iter().map(crate::types::HashedBlockHeader::from).collect::>(), + ) + .await + .unwrap(); // Filter headers available up to 100, wallet at genesis (scan_start = 0) manager.progress.update_filter_header_tip_height(100); @@ -2053,7 +2080,15 @@ mod tests { // Store block headers so start_download can resolve heights let headers = dashcore::block::Header::dummy_batch(0..101); - manager.header_storage.write().await.store_headers(&headers).await.unwrap(); + manager + .header_storage + .write() + .await + .store_headers( + &headers.iter().map(crate::types::HashedBlockHeader::from).collect::>(), + ) + .await + .unwrap(); // Simulate restart where everything is already synced but state is WaitForEvents. // committed == stored == filter_header_tip — start_download detects synced state. @@ -2096,7 +2131,15 @@ mod tests { // intentionally left empty: with the old behavior the lookahead batch // reads from height 0 and trips the empty-segment guard. let headers = dashcore::block::Header::dummy_batch(0..102); - manager.header_storage.write().await.store_headers(&headers).await.unwrap(); + manager + .header_storage + .write() + .await + .store_headers( + &headers.iter().map(crate::types::HashedBlockHeader::from).collect::>(), + ) + .await + .unwrap(); manager.set_state(SyncState::WaitForEvents); manager.wallet.write().await.update_wallet_synced_height(&MOCK_WALLET_ID, 100); @@ -2171,7 +2214,15 @@ mod tests { // header; the rest are dummies and never get matched against. let mut headers: Vec = dashcore::block::Header::dummy_batch(0..201); headers[50] = block_at_50.header; - manager.header_storage.write().await.store_headers(&headers).await.unwrap(); + manager + .header_storage + .write() + .await + .store_headers( + &headers.iter().map(crate::types::HashedBlockHeader::from).collect::>(), + ) + .await + .unwrap(); // Persist a filter at every height in 0..=100 so `load_filters` over // the initial batch range succeeds. Non-matching heights get a @@ -2373,7 +2424,15 @@ mod tests { let mut manager = create_test_manager().await; let headers = dashcore::block::Header::dummy_batch(0..101); - manager.header_storage.write().await.store_headers(&headers).await.unwrap(); + manager + .header_storage + .write() + .await + .store_headers( + &headers.iter().map(crate::types::HashedBlockHeader::from).collect::>(), + ) + .await + .unwrap(); // Populate filter storage so load_filters succeeds during lookahead { @@ -2434,7 +2493,15 @@ mod tests { let mut manager = create_test_manager().await; let headers = dashcore::block::Header::dummy_batch(0..101); - manager.header_storage.write().await.store_headers(&headers).await.unwrap(); + manager + .header_storage + .write() + .await + .store_headers( + &headers.iter().map(crate::types::HashedBlockHeader::from).collect::>(), + ) + .await + .unwrap(); { let dummy_filter = BlockFilter::new(&[0u8; 32]); let mut fs = manager.filter_storage.write().await; diff --git a/dash-spv/src/sync/filters/pipeline.rs b/dash-spv/src/sync/filters/pipeline.rs index 3f2c2a4d5..acfbd3906 100644 --- a/dash-spv/src/sync/filters/pipeline.rs +++ b/dash-spv/src/sync/filters/pipeline.rs @@ -155,8 +155,8 @@ impl FiltersPipeline { // Get stop hash for this batch. If the header isn't available yet, // re-queue for the next tick instead of losing the batch permanently. - let header = match storage.get_header(batch_end).await { - Ok(Some(h)) => h, + let stop_hash = match storage.get_header(batch_end).await { + Ok(Some(h)) => *h.hash(), Ok(None) => { tracing::debug!( "Header at height {} not yet available, re-queuing filter batch {}", @@ -178,7 +178,7 @@ impl FiltersPipeline { } }; - requests.request_filters(start_height, header.block_hash())?; + requests.request_filters(start_height, stop_hash)?; self.coordinator.mark_sent(&[start_height]); @@ -431,7 +431,12 @@ mod tests { let headers = Header::dummy_batch(0..6000); let tmp_dir = TempDir::new().unwrap(); let mut storage = PersistentBlockHeaderStorage::open(tmp_dir.path()).await.unwrap(); - storage.store_headers(&headers).await.unwrap(); + storage + .store_headers( + &headers.iter().map(crate::types::HashedBlockHeader::from).collect::>(), + ) + .await + .unwrap(); let mut pipeline = FiltersPipeline::new(); pipeline.init(0, 3500); @@ -829,7 +834,12 @@ mod tests { let headers = Header::dummy_batch(0..1000); let tmp_dir = TempDir::new().unwrap(); let mut storage = PersistentBlockHeaderStorage::open(tmp_dir.path()).await.unwrap(); - storage.store_headers(&headers).await.unwrap(); + storage + .store_headers( + &headers.iter().map(crate::types::HashedBlockHeader::from).collect::>(), + ) + .await + .unwrap(); let mut pipeline = FiltersPipeline::new(); pipeline.init(0, 999); @@ -863,7 +873,12 @@ mod tests { let headers = Header::dummy_batch(0..25000); let tmp_dir = TempDir::new().unwrap(); let mut storage = PersistentBlockHeaderStorage::open(tmp_dir.path()).await.unwrap(); - storage.store_headers(&headers).await.unwrap(); + storage + .store_headers( + &headers.iter().map(crate::types::HashedBlockHeader::from).collect::>(), + ) + .await + .unwrap(); let mut pipeline = FiltersPipeline::new(); pipeline.init(0, 24999); @@ -884,7 +899,12 @@ mod tests { let headers = Header::dummy_batch(0..1500); let tmp_dir = TempDir::new().unwrap(); let mut storage = PersistentBlockHeaderStorage::open(tmp_dir.path()).await.unwrap(); - storage.store_headers(&headers).await.unwrap(); + storage + .store_headers( + &headers.iter().map(crate::types::HashedBlockHeader::from).collect::>(), + ) + .await + .unwrap(); let mut pipeline = FiltersPipeline::new(); // Target is 1200, so second batch ends at 1200 not 1999 @@ -910,7 +930,12 @@ mod tests { let headers = Header::dummy_batch(0..3000); let tmp_dir = TempDir::new().unwrap(); let mut storage = PersistentBlockHeaderStorage::open(tmp_dir.path()).await.unwrap(); - storage.store_headers(&headers).await.unwrap(); + storage + .store_headers( + &headers.iter().map(crate::types::HashedBlockHeader::from).collect::>(), + ) + .await + .unwrap(); let mut pipeline = FiltersPipeline::new(); pipeline.init(0, 2500); @@ -930,7 +955,12 @@ mod tests { let headers = Header::dummy_batch(0..100); let tmp_dir = TempDir::new().unwrap(); let mut storage = PersistentBlockHeaderStorage::open(tmp_dir.path()).await.unwrap(); - storage.store_headers(&headers).await.unwrap(); + storage + .store_headers( + &headers.iter().map(crate::types::HashedBlockHeader::from).collect::>(), + ) + .await + .unwrap(); let mut pipeline = FiltersPipeline::new(); pipeline.init(0, 50); @@ -955,7 +985,12 @@ mod tests { let headers = Header::dummy_batch(0..100); let tmp_dir = TempDir::new().unwrap(); let mut storage = PersistentBlockHeaderStorage::open(tmp_dir.path()).await.unwrap(); - storage.store_headers(&headers).await.unwrap(); + storage + .store_headers( + &headers.iter().map(crate::types::HashedBlockHeader::from).collect::>(), + ) + .await + .unwrap(); let mut pipeline = FiltersPipeline::new(); pipeline.init(0, 99); @@ -990,7 +1025,12 @@ mod tests { let headers = Header::dummy_batch(0..1000); let tmp_dir = TempDir::new().unwrap(); let mut storage = PersistentBlockHeaderStorage::open(tmp_dir.path()).await.unwrap(); - storage.store_headers(&headers).await.unwrap(); + storage + .store_headers( + &headers.iter().map(crate::types::HashedBlockHeader::from).collect::>(), + ) + .await + .unwrap(); let mut pipeline = create_pipeline_with_short_timeout(); pipeline.init(0, 999); @@ -1053,7 +1093,12 @@ mod tests { let headers = Header::dummy_batch(0..5000); let tmp_dir = TempDir::new().unwrap(); let mut storage = PersistentBlockHeaderStorage::open(tmp_dir.path()).await.unwrap(); - storage.store_headers(&headers).await.unwrap(); + storage + .store_headers( + &headers.iter().map(crate::types::HashedBlockHeader::from).collect::>(), + ) + .await + .unwrap(); let mut pipeline = create_pipeline_with_low_concurrency(); pipeline.init(0, 2500); @@ -1089,7 +1134,12 @@ mod tests { let headers = Header::dummy_batch(0..1000); let tmp_dir = TempDir::new().unwrap(); let mut storage = PersistentBlockHeaderStorage::open(tmp_dir.path()).await.unwrap(); - storage.store_headers(&headers).await.unwrap(); + storage + .store_headers( + &headers.iter().map(crate::types::HashedBlockHeader::from).collect::>(), + ) + .await + .unwrap(); let mut pipeline = FiltersPipeline::new(); pipeline.init(0, 1999); @@ -1114,7 +1164,12 @@ mod tests { // Store the missing headers and retry let more_headers = Header::dummy_batch(1000..2000); - storage.store_headers(&more_headers).await.unwrap(); + storage + .store_headers( + &more_headers.iter().map(crate::types::HashedBlockHeader::from).collect::>(), + ) + .await + .unwrap(); let sent = pipeline.send_pending(&sender, &storage).await.unwrap(); assert_eq!(sent, 1); diff --git a/dash-spv/src/sync/masternodes/manager.rs b/dash-spv/src/sync/masternodes/manager.rs index a4e042274..b31101586 100644 --- a/dash-spv/src/sync/masternodes/manager.rs +++ b/dash-spv/src/sync/masternodes/manager.rs @@ -654,7 +654,17 @@ mod tests { ) -> (TestMasternodesManager, RequestSender, mpsc::UnboundedReceiver) { let storage = DiskStorageManager::with_temp_dir().await.unwrap(); let block_headers = storage.block_headers(); - block_headers.write().await.store_headers(&Header::dummy_batch(0..tip + 1)).await.unwrap(); + block_headers + .write() + .await + .store_headers( + &Header::dummy_batch(0..tip + 1) + .iter() + .map(crate::types::HashedBlockHeader::from) + .collect::>(), + ) + .await + .unwrap(); let engine = engine_with_lists(&[(tip, 1)]); let mut manager = MasternodesManager::new( block_headers, diff --git a/dash-spv/src/sync/masternodes/sync_manager.rs b/dash-spv/src/sync/masternodes/sync_manager.rs index 120bb0d78..e949c0d2d 100644 --- a/dash-spv/src/sync/masternodes/sync_manager.rs +++ b/dash-spv/src/sync/masternodes/sync_manager.rs @@ -89,7 +89,7 @@ pub(super) async fn build_mnlistdiff_request_pairs( let base_hash = if let Some(height) = base_height { match storage.get_header(height).await { - Ok(Some(h)) => h.block_hash(), + Ok(Some(h)) => *h.hash(), Ok(None) => { tracing::warn!("Base header not found at height {}, using all-zeros", height); BlockHash::all_zeros() @@ -109,7 +109,7 @@ pub(super) async fn build_mnlistdiff_request_pairs( }; let target_hash = match storage.get_header(validation_height).await { - Ok(Some(h)) => h.block_hash(), + Ok(Some(h)) => *h.hash(), Ok(None) => { tracing::warn!("Target header not found at height {}, skipping", validation_height); continue; @@ -187,7 +187,7 @@ pub(super) async fn feed_qrinfo_heights_to_engine( let cycle_boundary_height = work_block_height + WORK_DIFF_DEPTH; if let Ok(Some(cycle_boundary_header)) = storage.get_header(cycle_boundary_height).await { - let cycle_boundary_hash = cycle_boundary_header.block_hash(); + let cycle_boundary_hash = *cycle_boundary_header.hash(); engine.feed_block_height(cycle_boundary_height, cycle_boundary_hash); fed_count += 1; tracing::debug!( @@ -654,7 +654,6 @@ mod tests { use crate::storage::{BlockHeaderStorage, BlockHeaderTip}; use crate::types::HashedBlockHeader; use async_trait::async_trait; - use dashcore::block::Header as BlockHeader; use dashcore::bls_sig_utils::{BLSPublicKey, BLSSignature}; use dashcore::hash_types::QuorumVVecHash; use dashcore::network::message_qrinfo::{MNSkipListMode, QRInfo, QuorumSnapshot}; @@ -672,27 +671,17 @@ mod tests { #[async_trait] impl BlockHeaderStorage for MockHeaderStorage { - async fn store_headers(&mut self, _: &[BlockHeader]) -> StorageResult<()> { + async fn store_headers(&mut self, _: &[HashedBlockHeader]) -> StorageResult<()> { Ok(()) } async fn store_headers_at_height( - &mut self, - _: &[BlockHeader], - _: u32, - ) -> StorageResult<()> { - Ok(()) - } - async fn store_hashed_headers(&mut self, _: &[HashedBlockHeader]) -> StorageResult<()> { - Ok(()) - } - async fn store_hashed_headers_at_height( &mut self, _: &[HashedBlockHeader], _: u32, ) -> StorageResult<()> { Ok(()) } - async fn load_headers(&self, _: Range) -> StorageResult> { + async fn load_headers(&self, _: Range) -> StorageResult> { Ok(vec![]) } async fn get_tip_height(&self) -> Option { diff --git a/key-wallet-manager/src/event_tests.rs b/key-wallet-manager/src/event_tests.rs index 516c5356f..286896921 100644 --- a/key-wallet-manager/src/event_tests.rs +++ b/key-wallet-manager/src/event_tests.rs @@ -283,7 +283,7 @@ async fn test_late_instant_send_lock_after_block_confirmation_emits_event() { // Confirm the transaction in a block first. let block = make_block(vec![tx.clone()], 0xe3, 4000); let wallets = BTreeSet::from([wallet_id]); - manager.process_block_for_wallets(&block, 300, &wallets).await; + manager.process_block_for_wallets(&block, block.block_hash(), 300, &wallets).await; let mut rx = manager.subscribe_events(); let lock = InstantLock { @@ -332,7 +332,7 @@ async fn test_block_with_new_tx_emits_inserted_record() { let block = make_block(vec![tx.clone()], 0xcc, 1000); let wallets = BTreeSet::from([wallet_id]); - let result = manager.process_block_for_wallets(&block, 100, &wallets).await; + let result = manager.process_block_for_wallets(&block, block.block_hash(), 100, &wallets).await; assert_eq!(result.new_txids.len(), 1); let events = drain_events(&mut rx); @@ -399,7 +399,7 @@ async fn test_block_confirming_known_mempool_tx_emits_updated_record() { let mut rx = manager.subscribe_events(); let block = make_block(vec![tx.clone()], 0xdd, 2000); let wallets = BTreeSet::from([wallet_id]); - manager.process_block_for_wallets(&block, 200, &wallets).await; + manager.process_block_for_wallets(&block, block.block_hash(), 200, &wallets).await; let events = drain_events(&mut rx); assert_eq!(events.len(), 1, "one BlockProcessed expected, got {:?}", events); @@ -502,7 +502,7 @@ async fn test_block_with_index_less_account_tx_carries_account_type() { let mut rx = manager.subscribe_events(); let block = make_block(vec![tx.clone()], 0xee, 9999); let wallets = BTreeSet::from([wallet_id]); - manager.process_block_for_wallets(&block, 9000, &wallets).await; + manager.process_block_for_wallets(&block, block.block_hash(), 9000, &wallets).await; let events = drain_events(&mut rx); let block_event = events @@ -540,7 +540,7 @@ async fn test_empty_block_for_idle_wallet_emits_nothing() { let block = make_block(Vec::new(), 0x55, 3000); let wallets = BTreeSet::from([wallet_id]); - manager.process_block_for_wallets(&block, 50, &wallets).await; + manager.process_block_for_wallets(&block, block.block_hash(), 50, &wallets).await; assert_no_events(&mut rx); } @@ -555,13 +555,27 @@ async fn test_block_processed_carries_matured_coinbase_record() { let coinbase_height = 100; let coinbase_block = make_block(vec![coinbase_tx.clone()], 0xc0, 4000); let wallets = BTreeSet::from([wallet_id]); - manager.process_block_for_wallets(&coinbase_block, coinbase_height, &wallets).await; + manager + .process_block_for_wallets( + &coinbase_block, + coinbase_block.block_hash(), + coinbase_height, + &wallets, + ) + .await; // Advance to maturity height. With coinbase_height = 100, maturity is at // height 200. Processing block 200 must surface the matured record. let mut rx = manager.subscribe_events(); let mature_block = make_block(Vec::new(), 0xc1, 5000); - manager.process_block_for_wallets(&mature_block, coinbase_height + 100, &wallets).await; + manager + .process_block_for_wallets( + &mature_block, + mature_block.block_hash(), + coinbase_height + 100, + &wallets, + ) + .await; let events = drain_events(&mut rx); let block_event = events @@ -903,7 +917,7 @@ async fn test_block_with_external_and_internal_high_index_extends_both_pools() { let mut rx = manager.subscribe_events(); let wallets = BTreeSet::from([wallet_id]); - manager.process_block_for_wallets(&block, 700, &wallets).await; + manager.process_block_for_wallets(&block, block.block_hash(), 700, &wallets).await; let events = drain_events(&mut rx); let block_event = events @@ -967,7 +981,7 @@ async fn test_block_with_two_records_pushing_external_boundary_dedupes() { let mut rx = manager.subscribe_events(); let wallets = BTreeSet::from([wallet_id]); - manager.process_block_for_wallets(&block, 800, &wallets).await; + manager.process_block_for_wallets(&block, block.block_hash(), 800, &wallets).await; let events = drain_events(&mut rx); let block_event = events @@ -1054,7 +1068,7 @@ async fn test_apply_chain_lock_promotes_in_block_record_and_emits_event() { let tx = create_tx_paying_to(&addr, 0xa1); let block = make_block(vec![tx.clone()], 0xa1, 1000); let wallets = BTreeSet::from([wallet_id]); - manager.process_block_for_wallets(&block, 100, &wallets).await; + manager.process_block_for_wallets(&block, block.block_hash(), 100, &wallets).await; let mut rx = manager.subscribe_events(); manager.apply_chain_lock(ChainLock::dummy(100)); @@ -1125,7 +1139,7 @@ async fn test_apply_chain_lock_with_no_records_emits_chain_lock_processed_and_ad let tx = create_tx_paying_to(&addr, 0xa2); let block = make_block(vec![tx.clone()], 0xa2, 1100); let wallets = BTreeSet::from([wallet_id]); - manager.process_block_for_wallets(&block, 100, &wallets).await; + manager.process_block_for_wallets(&block, block.block_hash(), 100, &wallets).await; let events = drain_events(&mut rx); let bp = events @@ -1161,7 +1175,7 @@ async fn test_apply_chain_lock_is_idempotent_on_already_finalized() { let tx = create_tx_paying_to(&addr, 0xa3); let block = make_block(vec![tx.clone()], 0xa3, 1200); let wallets = BTreeSet::from([wallet_id]); - manager.process_block_for_wallets(&block, 50, &wallets).await; + manager.process_block_for_wallets(&block, block.block_hash(), 50, &wallets).await; let mut rx = manager.subscribe_events(); manager.apply_chain_lock(ChainLock::dummy(50)); @@ -1228,7 +1242,7 @@ async fn test_block_processed_chainlocked_flag_matches_record_context() { let block_below = make_block(vec![tx_below.clone()], 0xa4, 1300); let wallets = BTreeSet::from([wallet_id]); let mut rx = manager.subscribe_events(); - manager.process_block_for_wallets(&block_below, 500, &wallets).await; + manager.process_block_for_wallets(&block_below, block_below.block_hash(), 500, &wallets).await; let events_below = drain_events(&mut rx); let bp_below = events_below @@ -1252,7 +1266,7 @@ async fn test_block_processed_chainlocked_flag_matches_record_context() { .expect("address generation"); let tx_above = create_tx_paying_to(&addr2, 0xa5); let block_above = make_block(vec![tx_above.clone()], 0xa5, 1400); - manager.process_block_for_wallets(&block_above, 2000, &wallets).await; + manager.process_block_for_wallets(&block_above, block_above.block_hash(), 2000, &wallets).await; let events_above = drain_events(&mut rx); let bp_above = events_above diff --git a/key-wallet-manager/src/process_block.rs b/key-wallet-manager/src/process_block.rs index 27ea4cba5..c340693ca 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, ScriptBuf, Transaction}; +use dashcore::{Address, Block, BlockHash, ScriptBuf, Transaction}; use key_wallet::account::AccountType; use key_wallet::managed_account::transaction_record::TransactionRecord; use key_wallet::transaction_checking::{BlockInfo, DerivedAddressInfo, TransactionContext}; @@ -20,6 +20,7 @@ impl WalletInterface for WalletM async fn process_block_for_wallets( &mut self, block: &Block, + block_hash: BlockHash, height: CoreBlockHeight, wallets: &BTreeSet, ) -> BlockProcessingResult { @@ -27,7 +28,7 @@ impl WalletInterface for WalletM if wallets.is_empty() { return result; } - let info = BlockInfo::new(height, block.block_hash(), block.header.time); + let info = BlockInfo::new(height, block_hash, block.header.time); // Late-block: when every wallet in scope already has its // finality boundary at or above this height, the block is @@ -587,7 +588,7 @@ mod tests { let mut rx = manager.subscribe_events(); let wallets = BTreeSet::from([wallet_id]); - manager.process_block_for_wallets(&block, 100, &wallets).await; + manager.process_block_for_wallets(&block, block.block_hash(), 100, &wallets).await; let mut found = false; while let Ok(event) = rx.try_recv() { @@ -646,18 +647,18 @@ mod tests { let block = make_block(vec![]); let only_w1 = BTreeSet::from([wallet_id1]); - manager.process_block_for_wallets(&block, 200, &only_w1).await; + manager.process_block_for_wallets(&block, block.block_hash(), 200, &only_w1).await; assert_eq!(manager.get_wallet_info(&wallet_id1).unwrap().last_processed_height(), 200); assert_eq!(manager.get_wallet_info(&wallet_id2).unwrap().last_processed_height(), 0); let only_w2 = BTreeSet::from([wallet_id2]); - manager.process_block_for_wallets(&block, 300, &only_w2).await; + manager.process_block_for_wallets(&block, block.block_hash(), 300, &only_w2).await; assert_eq!(manager.get_wallet_info(&wallet_id1).unwrap().last_processed_height(), 200); assert_eq!(manager.get_wallet_info(&wallet_id2).unwrap().last_processed_height(), 300); // Empty wallet set is a no-op even though the height is past both wallets. let none = BTreeSet::new(); - manager.process_block_for_wallets(&block, 1000, &none).await; + manager.process_block_for_wallets(&block, block.block_hash(), 1000, &none).await; assert_eq!(manager.get_wallet_info(&wallet_id1).unwrap().last_processed_height(), 200); assert_eq!(manager.get_wallet_info(&wallet_id2).unwrap().last_processed_height(), 300); } @@ -773,7 +774,9 @@ mod tests { let tx2 = create_tx_paying_to(&addr, 0xd1); let block = make_block(vec![tx2]); let block_wallets = BTreeSet::from([wallet_id]); - let _result = manager.process_block_for_wallets(&block, 100, &block_wallets).await; + let _result = manager + .process_block_for_wallets(&block, block.block_hash(), 100, &block_wallets) + .await; assert!( manager.monitor_revision() > rev_before_block, "block with tx paying to our address should bump revision (UTXO added)" diff --git a/key-wallet-manager/src/test_utils/mock_wallet.rs b/key-wallet-manager/src/test_utils/mock_wallet.rs index 2e9118625..21ee92d0c 100644 --- a/key-wallet-manager/src/test_utils/mock_wallet.rs +++ b/key-wallet-manager/src/test_utils/mock_wallet.rs @@ -128,6 +128,7 @@ impl WalletInterface for MockWallet { async fn process_block_for_wallets( &mut self, block: &Block, + _block_hash: dashcore::BlockHash, height: u32, wallets: &BTreeSet, ) -> BlockProcessingResult { @@ -290,6 +291,7 @@ impl WalletInterface for NonMatchingMockWallet { async fn process_block_for_wallets( &mut self, _block: &Block, + _block_hash: dashcore::BlockHash, height: u32, wallets: &BTreeSet, ) -> BlockProcessingResult { @@ -425,6 +427,7 @@ impl WalletInterface for MultiMockWallet { async fn process_block_for_wallets( &mut self, block: &Block, + _block_hash: dashcore::BlockHash, height: CoreBlockHeight, wallets: &BTreeSet, ) -> BlockProcessingResult { diff --git a/key-wallet-manager/src/wallet_interface.rs b/key-wallet-manager/src/wallet_interface.rs index 4974f8a75..99772b096 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, ScriptBuf, Transaction, Txid}; +use dashcore::{Address, Block, BlockHash, OutPoint, ScriptBuf, Transaction, Txid}; use std::collections::{BTreeMap, BTreeSet}; use tokio::sync::broadcast; @@ -68,6 +68,7 @@ pub trait WalletInterface: Send + Sync + 'static { async fn process_block_for_wallets( &mut self, block: &Block, + block_hash: BlockHash, height: CoreBlockHeight, wallets: &BTreeSet, ) -> BlockProcessingResult; diff --git a/key-wallet-manager/tests/spv_integration_tests.rs b/key-wallet-manager/tests/spv_integration_tests.rs index fe26aa8a9..46ada40f3 100644 --- a/key-wallet-manager/tests/spv_integration_tests.rs +++ b/key-wallet-manager/tests/spv_integration_tests.rs @@ -17,7 +17,7 @@ async fn process_block_all_wallets( height: u32, ) -> BlockProcessingResult { let wallet_ids: BTreeSet = manager.list_wallets().into_iter().copied().collect(); - manager.process_block_for_wallets(block, height, &wallet_ids).await + manager.process_block_for_wallets(block, block.block_hash(), height, &wallet_ids).await } #[tokio::test]