Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
72d2371
feat(storage): add reorg-in-progress sentinel and `delete_metadata` t…
xdustinface May 26, 2026
13b4c5e
feat(dash-spv): write reorg sentinel before cascade and clear after s…
xdustinface May 26, 2026
ab0d42b
feat(storage): add `check_and_repair_consistency` for cross-storage i…
xdustinface May 26, 2026
ca43e60
feat(storage): compute valid tip via `header_hash_index` when sentine…
xdustinface May 26, 2026
a267c95
feat(storage): truncate cache to last readable segment when a segment…
xdustinface May 26, 2026
7918d21
feat(storage): validate and clear stale chainlock on startup
xdustinface May 26, 2026
8f8b382
docs(storage): document why the consistency check precedes the backgr…
xdustinface May 26, 2026
17aea59
feat(key-wallet): add `WalletInterface::clamp_heights_to` and call fr…
xdustinface May 26, 2026
3e25bea
test(storage): unit tests for sentinel and consistency repair
xdustinface May 26, 2026
b5c04da
chore: apply `cargo fmt` to startup consistency check files
xdustinface May 26, 2026
21834b0
chore: pr cleanup — move inline imports to module top, fix qualified …
xdustinface May 26, 2026
bc7beb9
fix(storage): address manki review findings for startup consistency PR
xdustinface May 26, 2026
7a5bfcd
test(storage): cover 'filters but no filter headers' path and block-s…
xdustinface May 26, 2026
f3d1ddd
fix(storage): clear orphaned filters when filter headers are absent o…
xdustinface May 26, 2026
c605fe2
fix(storage): include all on-disk segment IDs in `SegmentCache::clear…
xdustinface May 26, 2026
4372353
test(storage): verify `clear_all` durability survives reopen in consi…
xdustinface May 26, 2026
9ca1e8a
fix(dash-spv): drop block-header read guard before acquiring wallet lock
xdustinface May 26, 2026
60e2b9a
test(storage): cover sentinel-cleared path when block headers are empty
xdustinface May 26, 2026
0c1fa05
docs(key-wallet-manager): document `clamp_heights_to` as intentionall…
xdustinface May 26, 2026
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
15 changes: 13 additions & 2 deletions dash-spv/src/client/lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ use crate::chain::checkpoints::{mainnet_checkpoints, testnet_checkpoints, Checkp
use crate::error::{Result, SpvError};
use crate::network::NetworkManager;
use crate::storage::{
PersistentBlockHeaderStorage, PersistentBlockStorage, PersistentFilterHeaderStorage,
PersistentFilterStorage, PersistentMetadataStorage, StorageManager,
BlockHeaderStorage, PersistentBlockHeaderStorage, PersistentBlockStorage,
PersistentFilterHeaderStorage, PersistentFilterStorage, PersistentMetadataStorage,
StorageManager,
};
use crate::sync::{
BlockHeadersManager, BlocksManager, ChainLockManager, FilterHeadersManager, FiltersManager,
Expand Down Expand Up @@ -46,6 +47,16 @@ impl<W: WalletInterface, N: NetworkManager, S: StorageManager> DashSpvClient<W,
// so they can read the tip from storage during construction.
Self::initialize_genesis_block(&config, &mut storage).await?;

// Clamp wallet heights to the (possibly repaired) block-header tip so
// a mid-cascade crash recovery cannot leave wallet metadata pointing
Comment thread
xdustinface marked this conversation as resolved.
// above a height the local header chain no longer contains.
// Bind the tip into a local so the block-header read guard is dropped
// before the wallet write lock is acquired.
let tip_after_repair = storage.block_headers().read().await.get_tip_height().await;
if let Some(tip) = tip_after_repair {
wallet.write().await.clamp_heights_to(tip).await;
}

let masternode_engine = {
if config.enable_masternodes {
Some(Arc::new(RwLock::new(MasternodeListEngine::default_for_network(
Expand Down
43 changes: 43 additions & 0 deletions dash-spv/src/storage/block_headers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,49 @@ impl BlockHeaderStorage for PersistentBlockHeaderStorage {
}
}

impl PersistentBlockHeaderStorage {
Comment thread
manki-review[bot] marked this conversation as resolved.
/// Highest height `h` for which the immediate parent link
/// (`header_hash_index[prev_blockhash_of(h)] == h - 1`) is found in the
/// index and the header at `h` is present in storage. Walks backward from
/// the current tip, returning the first height that satisfies these two
/// conditions. Returns `None` when the storage is empty. Mid-chain gaps
/// are not detected; callers must not assume that `[start, result]` is
/// fully contiguous. Used by the startup consistency check to recover a
/// safe tip after a crash mid-cascade.
pub(crate) async fn highest_valid_tip(&mut self) -> Option<u32> {
let mut headers = self.block_headers.write().await;
let tip = headers.tip_height()?;
let start = headers.start_height()?;

let mut height = tip;
loop {
if height == start {
return Some(start);
}

let parent_height = height - 1;
let current = headers.get_item(height).await.ok().flatten();
let Some(current) = current else {
if height == 0 {
return None;
}
height -= 1;
continue;
};

match self.header_hash_index.get(&current.header().prev_blockhash) {
Some(&indexed) if indexed == parent_height => return Some(height),
_ => {
if height == 0 {
return None;
}
height -= 1;
}
}
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading
Loading