diff --git a/dash-spv/src/client/core.rs b/dash-spv/src/client/core.rs index f660c2b70..b41a7aeae 100644 --- a/dash-spv/src/client/core.rs +++ b/dash-spv/src/client/core.rs @@ -184,11 +184,7 @@ impl DashSpvClient Option { let storage = self.storage.lock().await; - - let tip_height = storage.get_tip_height().await?; - let header = storage.get_header(tip_height).await.ok()??; - - Some(header.block_hash()) + storage.get_tip().await.map(|tip| *tip.hash()) } /// Returns the current chain tip height (absolute), accounting for checkpoint base. diff --git a/dash-spv/src/storage/blocks.rs b/dash-spv/src/storage/blocks.rs index 7d17f45dd..36d8dabfb 100644 --- a/dash-spv/src/storage/blocks.rs +++ b/dash-spv/src/storage/blocks.rs @@ -4,15 +4,41 @@ use std::collections::HashMap; use std::ops::Range; use std::path::PathBuf; +use crate::error::StorageResult; +use crate::storage::segments::SegmentCache; +use crate::storage::PersistentStorage; +use crate::types::HashedBlockHeader; use async_trait::async_trait; use dashcore::block::Header as BlockHeader; +use dashcore::prelude::CoreBlockHeight; use dashcore::BlockHash; use tokio::sync::RwLock; -use crate::error::StorageResult; -use crate::storage::segments::SegmentCache; -use crate::storage::PersistentStorage; -use crate::types::HashedBlockHeader; +#[derive(Debug, PartialEq)] +pub struct BlockHeaderTip { + height: CoreBlockHeight, + header: BlockHeader, + hash: BlockHash, +} + +impl BlockHeaderTip { + pub fn new(height: CoreBlockHeight, hashed_block_header: HashedBlockHeader) -> Self { + Self { + height, + header: *hashed_block_header.header(), + hash: *hashed_block_header.hash(), + } + } + pub fn height(&self) -> CoreBlockHeight { + self.height + } + pub fn header(&self) -> &BlockHeader { + &self.header + } + pub fn hash(&self) -> &BlockHash { + &self.hash + } +} #[async_trait] pub trait BlockHeaderStorage { @@ -48,6 +74,8 @@ pub trait BlockHeaderStorage { async fn get_tip_height(&self) -> Option; + async fn get_tip(&self) -> Option; + async fn get_start_height(&self) -> Option; async fn get_stored_headers_len(&self) -> u32; @@ -146,6 +174,14 @@ impl BlockHeaderStorage for PersistentBlockHeaderStorage { self.block_headers.read().await.tip_height() } + async fn get_tip(&self) -> Option { + let mut block_headers = self.block_headers.write().await; + let tip_height = block_headers.tip_height()?; + let hashed_header = + block_headers.get_items(tip_height..tip_height + 1).await.ok()?.into_iter().next()?; + Some(BlockHeaderTip::new(tip_height, hashed_header)) + } + async fn get_start_height(&self) -> Option { self.block_headers.read().await.start_height() } @@ -175,3 +211,30 @@ impl BlockHeaderStorage for PersistentBlockHeaderStorage { Ok(self.header_hash_index.get(hash).copied()) } } + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::TempDir; + + #[tokio::test] + async fn test_get_tip() { + let headers = BlockHeader::dummy_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 + assert!(storage.get_tip().await.is_none()); + // 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])); + 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])); + assert_eq!(tip, expected_tip); + assert_eq!(storage.get_tip_height().await, Some(4)); + } +} diff --git a/dash-spv/src/storage/mod.rs b/dash-spv/src/storage/mod.rs index a67d2ec98..b7dca34f0 100644 --- a/dash-spv/src/storage/mod.rs +++ b/dash-spv/src/storage/mod.rs @@ -24,7 +24,7 @@ use std::time::Duration; use tokio::sync::RwLock; use crate::error::StorageResult; -use crate::storage::blocks::PersistentBlockHeaderStorage; +use crate::storage::blocks::{BlockHeaderTip, PersistentBlockHeaderStorage}; use crate::storage::chainstate::PersistentChainStateStorage; use crate::storage::filters::{PersistentFilterHeaderStorage, PersistentFilterStorage}; use crate::storage::lockfile::LockFile; @@ -273,6 +273,10 @@ impl blocks::BlockHeaderStorage for DiskStorageManager { self.block_headers.read().await.get_tip_height().await } + async fn get_tip(&self) -> Option { + self.block_headers.read().await.get_tip().await + } + async fn get_start_height(&self) -> Option { self.block_headers.read().await.get_start_height().await }