Skip to content

Commit eca3330

Browse files
authored
fix: avoid panic for sentinel blocks in debug builds (#427)
The workaround with the available heights also doesn't work in debug builds. I just didn't realize since I was always running release builds lately. Now while working on integration tests, this became an issue again.
1 parent 67bbddb commit eca3330

File tree

2 files changed

+23
-27
lines changed

2 files changed

+23
-27
lines changed

dash-spv/src/storage/blocks.rs

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
//! Block storage for persisting full blocks that contain wallet-relevant transactions.
22
3-
use std::collections::HashSet;
43
use std::path::PathBuf;
54

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

3733
impl PersistentBlockStorage {
@@ -46,24 +42,10 @@ impl PersistentStorage for PersistentBlockStorage {
4642

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

49-
let mut blocks: SegmentCache<HashedBlock> =
50-
SegmentCache::load_or_new(&blocks_folder).await?;
51-
52-
let mut available_heights = HashSet::new();
53-
54-
if let (Some(start), Some(end)) = (blocks.start_height(), blocks.tip_height()) {
55-
let hashed_blocks = blocks.get_items(start..end + 1).await?;
56-
let sentinel = HashedBlock::sentinel();
57-
for (i, hashed_block) in hashed_blocks.iter().enumerate() {
58-
if hashed_block != &sentinel {
59-
available_heights.insert(start + i as CoreBlockHeight);
60-
}
61-
}
62-
}
45+
let blocks: SegmentCache<HashedBlock> = SegmentCache::load_or_new(&blocks_folder).await?;
6346

6447
Ok(Self {
6548
blocks: RwLock::new(blocks),
66-
available_heights,
6749
})
6850
}
6951

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

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

dash-spv/src/storage/segments.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,20 @@ impl<I: Persistable> SegmentCache<I> {
260260
Ok(items)
261261
}
262262

263+
/// Get a single item by height. Returns `None` for sentinel (empty) slots.
264+
/// Unlike `get_items()`, this does not assert dense storage — safe for sparse data.
265+
pub async fn get_item(&mut self, height: u32) -> StorageResult<Option<I>> {
266+
let segment_id = Self::height_to_segment_id(height);
267+
let offset = Self::height_to_offset(height);
268+
let segment = self.get_segment_mut(&segment_id).await?;
269+
let item = segment.get_single(offset);
270+
if *item == I::sentinel() {
271+
Ok(None)
272+
} else {
273+
Ok(Some(item.clone()))
274+
}
275+
}
276+
263277
pub async fn store_items(&mut self, items: &[I]) -> StorageResult<()> {
264278
self.store_items_at_height(items, self.next_height()).await
265279
}
@@ -479,6 +493,12 @@ impl<I: Persistable> Segment<I> {
479493
self.last_accessed = std::time::Instant::now();
480494
}
481495

496+
/// Get a single item by offset, returning the raw value (may be a sentinel).
497+
pub fn get_single(&mut self, offset: u32) -> &I {
498+
self.last_accessed = Instant::now();
499+
&self.items[offset as usize]
500+
}
501+
482502
pub fn get(&mut self, range: Range<u32>) -> &[I] {
483503
debug_assert!(range.start < self.items.len() as u32);
484504
debug_assert!(range.end <= self.items.len() as u32);

0 commit comments

Comments
 (0)