Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 3 additions & 27 deletions dash-spv/src/storage/blocks.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
//! Block storage for persisting full blocks that contain wallet-relevant transactions.

use std::collections::HashSet;
use std::path::PathBuf;

use crate::error::StorageResult;
use crate::storage::segments::{Persistable, SegmentCache};
use crate::storage::segments::SegmentCache;
use crate::storage::PersistentStorage;
use crate::types::HashedBlock;
use async_trait::async_trait;
Expand All @@ -29,9 +28,6 @@ pub trait BlockStorage: Send + Sync + 'static {
pub struct PersistentBlockStorage {
/// Block storage segments.
blocks: RwLock<SegmentCache<HashedBlock>>,
/// Set of available block heights used for fast lookups and to bypass sentinel loading and gap
/// detection asserts (in debug builds) in the underlying segment implementation.
available_heights: HashSet<CoreBlockHeight>,
}

impl PersistentBlockStorage {
Expand All @@ -46,24 +42,10 @@ impl PersistentStorage for PersistentBlockStorage {

tracing::debug!("Opening PersistentBlockStorage from {:?}", blocks_folder);

let mut blocks: SegmentCache<HashedBlock> =
SegmentCache::load_or_new(&blocks_folder).await?;

let mut available_heights = HashSet::new();

if let (Some(start), Some(end)) = (blocks.start_height(), blocks.tip_height()) {
let hashed_blocks = blocks.get_items(start..end + 1).await?;
let sentinel = HashedBlock::sentinel();
for (i, hashed_block) in hashed_blocks.iter().enumerate() {
if hashed_block != &sentinel {
available_heights.insert(start + i as CoreBlockHeight);
}
}
}
let blocks: SegmentCache<HashedBlock> = SegmentCache::load_or_new(&blocks_folder).await?;

Ok(Self {
blocks: RwLock::new(blocks),
available_heights,
})
}

Expand All @@ -78,17 +60,11 @@ impl PersistentStorage for PersistentBlockStorage {
#[async_trait]
impl BlockStorage for PersistentBlockStorage {
async fn store_block(&mut self, height: u32, hashed_block: HashedBlock) -> StorageResult<()> {
self.available_heights.insert(height);
self.blocks.write().await.store_items_at_height(&[hashed_block], height).await
}

async fn load_block(&self, height: u32) -> StorageResult<Option<HashedBlock>> {
// This early return avoids unnecessary disk lookups and bypasses sentinel loading and gap
// detection asserts (in debug builds) in the underlying segment implementation.
if !self.available_heights.contains(&height) {
return Ok(None);
}
Ok(self.blocks.write().await.get_items(height..height + 1).await?.first().cloned())
self.blocks.write().await.get_item(height).await
}
}

Expand Down
20 changes: 20 additions & 0 deletions dash-spv/src/storage/segments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,20 @@ impl<I: Persistable> SegmentCache<I> {
Ok(items)
}

/// Get a single item by height. Returns `None` for sentinel (empty) slots.
/// Unlike `get_items()`, this does not assert dense storage — safe for sparse data.
pub async fn get_item(&mut self, height: u32) -> StorageResult<Option<I>> {
let segment_id = Self::height_to_segment_id(height);
let offset = Self::height_to_offset(height);
let segment = self.get_segment_mut(&segment_id).await?;
let item = segment.get_single(offset);
if *item == I::sentinel() {
Ok(None)
} else {
Ok(Some(item.clone()))
}
}

pub async fn store_items(&mut self, items: &[I]) -> StorageResult<()> {
self.store_items_at_height(items, self.next_height()).await
}
Expand Down Expand Up @@ -479,6 +493,12 @@ impl<I: Persistable> Segment<I> {
self.last_accessed = std::time::Instant::now();
}

/// Get a single item by offset, returning the raw value (may be a sentinel).
pub fn get_single(&mut self, offset: u32) -> &I {
self.last_accessed = Instant::now();
&self.items[offset as usize]
}

pub fn get(&mut self, range: Range<u32>) -> &[I] {
debug_assert!(range.start < self.items.len() as u32);
debug_assert!(range.end <= self.items.len() as u32);
Expand Down
Loading