Skip to content

Commit 437b59c

Browse files
authored
refactor: Make synced_from_checkpoint based on sync_base_height (#226)
1 parent 42dd74d commit 437b59c

File tree

12 files changed

+33
-105
lines changed

12 files changed

+33
-105
lines changed

dash-spv-ffi/tests/unit/test_type_conversions.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,6 @@ mod tests {
171171
masternode_engine: None,
172172
last_masternode_diff_height: None,
173173
sync_base_height: 0,
174-
synced_from_checkpoint: false,
175174
};
176175

177176
let ffi_state = FFIChainState::from(state);

dash-spv/src/chain/fork_detector.rs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,7 @@ impl ForkDetector {
108108
// Check if this connects to the main chain (creates new fork)
109109
if let Ok(Some(height)) = storage.get_header_height(&prev_hash) {
110110
// Check if this would create a fork from before our checkpoint
111-
if chain_state.synced_from_checkpoint
112-
&& chain_state.sync_base_height > 0
113-
&& height < chain_state.sync_base_height
114-
{
111+
if chain_state.synced_from_checkpoint() && height < chain_state.sync_base_height {
115112
tracing::warn!(
116113
"Rejecting header that would create fork from height {} (before checkpoint base {}). \
117114
This likely indicates headers from genesis were received during checkpoint sync.",
@@ -140,11 +137,7 @@ impl ForkDetector {
140137
for (height, state_header) in chain_state.headers.iter().enumerate() {
141138
if prev_hash == state_header.block_hash() {
142139
// Calculate the actual blockchain height for this index
143-
let actual_height = if chain_state.synced_from_checkpoint {
144-
chain_state.sync_base_height + (height as u32)
145-
} else {
146-
height as u32
147-
};
140+
let actual_height = chain_state.sync_base_height + (height as u32);
148141

149142
// This connects to a header in chain state but not in storage
150143
// Treat it as extending main chain if it's the tip

dash-spv/src/chain/fork_detector_test.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ mod tests {
3232
let mut chain_state = ChainState::new();
3333

3434
// Simulate checkpoint sync from height 1000
35-
chain_state.synced_from_checkpoint = true;
3635
chain_state.sync_base_height = 1000;
3736

3837
// Add a checkpoint header at height 1000

dash-spv/src/chain/reorg.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ impl ReorgManager {
103103

104104
// Check reorg depth - account for checkpoint sync
105105
let reorg_depth = if let Some(state) = chain_state {
106-
if state.synced_from_checkpoint && state.sync_base_height > 0 {
106+
if state.synced_from_checkpoint() {
107107
// During checkpoint sync, both current_tip.height and fork.fork_height
108108
// should be interpreted relative to sync_base_height
109109

dash-spv/src/chain/reorg_test.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ mod tests {
9999
let storage = MemoryStorage::new();
100100

101101
// Simulate checkpoint sync from height 50000
102-
chain_state.synced_from_checkpoint = true;
103102
chain_state.sync_base_height = 50000;
104103

105104
// Current tip at height 50100

dash-spv/src/client/lifecycle.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ impl<
210210
tracing::error!("Failed to load headers into sync manager: {}", e);
211211
// For checkpoint sync, this is critical
212212
let state = self.state.read().await;
213-
if state.synced_from_checkpoint {
213+
if state.synced_from_checkpoint() {
214214
return Err(SpvError::Sync(e));
215215
}
216216
// For normal sync, we can continue as headers will be re-synced
@@ -398,11 +398,7 @@ impl<
398398
);
399399

400400
// Update the sync manager's cached flags from the checkpoint-initialized state
401-
self.sync_manager.update_chain_state_cache(
402-
true,
403-
checkpoint.height,
404-
headers_len,
405-
);
401+
self.sync_manager.update_chain_state_cache(checkpoint.height, headers_len);
406402
tracing::info!(
407403
"Updated sync manager with checkpoint-initialized chain state"
408404
);

dash-spv/src/client/sync_coordinator.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -917,12 +917,7 @@ impl<
917917
let target_height = saved_state.chain_tip.height;
918918

919919
// Determine first height to load. Skip genesis (already present) unless we started from a checkpoint base.
920-
let mut current_height =
921-
if saved_state.synced_from_checkpoint && saved_state.sync_base_height > 0 {
922-
saved_state.sync_base_height
923-
} else {
924-
1u32
925-
};
920+
let mut current_height = saved_state.sync_base_height.max(1);
926921

927922
while current_height <= target_height {
928923
let end_height = (current_height + BATCH_SIZE - 1).min(target_height);

dash-spv/src/storage/disk/state.rs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ impl DiskStorageManager {
2121

2222
// First store all headers
2323
// For checkpoint sync, we need to store headers starting from the checkpoint height
24-
if state.synced_from_checkpoint && state.sync_base_height > 0 && !state.headers.is_empty() {
24+
if state.synced_from_checkpoint() && !state.headers.is_empty() {
2525
// Store headers starting from the checkpoint height
2626
self.store_headers_from_height(&state.headers, state.sync_base_height).await?;
2727
} else {
@@ -38,7 +38,6 @@ impl DiskStorageManager {
3838
"current_filter_tip": state.current_filter_tip,
3939
"last_masternode_diff_height": state.last_masternode_diff_height,
4040
"sync_base_height": state.sync_base_height,
41-
"synced_from_checkpoint": state.synced_from_checkpoint,
4241
});
4342

4443
let path = self.base_path.join("state/chain.json");
@@ -63,11 +62,7 @@ impl DiskStorageManager {
6362

6463
// Load all headers
6564
if let Some(tip_height) = self.get_tip_height().await? {
66-
let range_start = if state.synced_from_checkpoint && state.sync_base_height > 0 {
67-
state.sync_base_height
68-
} else {
69-
0
70-
};
65+
let range_start = state.sync_base_height;
7166
state.headers = self.load_headers(range_start..tip_height + 1).await?;
7267
}
7368

@@ -88,8 +83,6 @@ impl DiskStorageManager {
8883
// Load checkpoint sync fields
8984
state.sync_base_height =
9085
value.get("sync_base_height").and_then(|v| v.as_u64()).map(|h| h as u32).unwrap_or(0);
91-
state.synced_from_checkpoint =
92-
value.get("synced_from_checkpoint").and_then(|v| v.as_bool()).unwrap_or(false);
9386

9487
Ok(Some(state))
9588
}
@@ -829,7 +822,6 @@ mod tests {
829822
// Set sync base height so storage interprets heights as blockchain heights
830823
let mut base_state = ChainState::new();
831824
base_state.sync_base_height = checkpoint_height;
832-
base_state.synced_from_checkpoint = true;
833825
storage.store_chain_state(&base_state).await?;
834826

835827
// Verify headers are stored at correct blockchain heights
@@ -861,7 +853,6 @@ mod tests {
861853
// Store chain state to persist sync_base_height
862854
let mut chain_state = ChainState::new();
863855
chain_state.sync_base_height = checkpoint_height;
864-
chain_state.synced_from_checkpoint = true;
865856
storage.store_chain_state(&chain_state).await?;
866857

867858
// Force save to disk

dash-spv/src/storage/sync_state.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,6 @@ pub struct PersistentSyncState {
4242

4343
/// Base height when syncing from a checkpoint (0 if syncing from genesis).
4444
pub sync_base_height: u32,
45-
46-
/// Whether the chain was synced from a checkpoint rather than genesis.
47-
pub synced_from_checkpoint: bool,
4845
}
4946

5047
/// Chain tip information.
@@ -195,7 +192,6 @@ impl PersistentSyncState {
195192
.map(|work| format!("{:?}", work))
196193
.unwrap_or_else(|| String::from("0")),
197194
sync_base_height: chain_state.sync_base_height,
198-
synced_from_checkpoint: chain_state.synced_from_checkpoint,
199195
})
200196
}
201197

@@ -324,6 +320,10 @@ impl PersistentSyncState {
324320
}
325321
}
326322

323+
pub fn synced_from_checkpoint(&self) -> bool {
324+
self.sync_base_height > 0
325+
}
326+
327327
/// Get the best checkpoint to use for recovery.
328328
pub fn get_best_checkpoint(&self) -> Option<&SyncCheckpoint> {
329329
self.checkpoints.iter().rev().find(|cp| cp.validated)
@@ -378,7 +378,6 @@ mod tests {
378378
saved_at: SystemTime::now(),
379379
chain_work: String::new(),
380380
sync_base_height: 0,
381-
synced_from_checkpoint: false,
382381
};
383382

384383
// Valid state

dash-spv/src/sync/headers/manager.rs

Lines changed: 13 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,7 @@ pub struct HeaderSyncManager<S: StorageManager, N: NetworkManager> {
5656
syncing_headers: bool,
5757
last_sync_progress: std::time::Instant,
5858
headers2_failed: bool,
59-
// Cached flags for quick access without locking
60-
cached_synced_from_checkpoint: bool,
59+
// Cached flag for quick access without locking
6160
cached_sync_base_height: u32,
6261
}
6362

@@ -94,7 +93,6 @@ impl<S: StorageManager + Send + Sync + 'static, N: NetworkManager + Send + Sync
9493
syncing_headers: false,
9594
last_sync_progress: std::time::Instant::now(),
9695
headers2_failed: false,
97-
cached_synced_from_checkpoint: false,
9896
cached_sync_base_height: 0,
9997
_phantom_s: std::marker::PhantomData,
10098
_phantom_n: std::marker::PhantomData,
@@ -106,13 +104,11 @@ impl<S: StorageManager + Send + Sync + 'static, N: NetworkManager + Send + Sync
106104
// First, try to load the persisted chain state which may contain sync_base_height
107105
if let Ok(Some(stored_chain_state)) = storage.load_chain_state().await {
108106
tracing::info!(
109-
"Loaded chain state from storage with sync_base_height: {}, synced_from_checkpoint: {}",
107+
"Loaded chain state from storage with sync_base_height: {}",
110108
stored_chain_state.sync_base_height,
111-
stored_chain_state.synced_from_checkpoint
112109
);
113110
// Update our chain state with the loaded one to preserve sync_base_height
114111
{
115-
self.cached_synced_from_checkpoint = stored_chain_state.synced_from_checkpoint;
116112
self.cached_sync_base_height = stored_chain_state.sync_base_height;
117113
let mut cs = self.chain_state.write().await;
118114
*cs = stored_chain_state;
@@ -128,7 +124,7 @@ impl<S: StorageManager + Send + Sync + 'static, N: NetworkManager + Send + Sync
128124
let Some(tip_height) = tip_height else {
129125
tracing::debug!("No headers found in storage");
130126
// If we're syncing from a checkpoint, this is expected
131-
if self.is_synced_from_checkpoint() && self.get_sync_base_height() > 0 {
127+
if self.is_synced_from_checkpoint() {
132128
tracing::info!("No headers in storage for checkpoint sync - this is expected");
133129
return Ok(0);
134130
}
@@ -147,15 +143,8 @@ impl<S: StorageManager + Send + Sync + 'static, N: NetworkManager + Send + Sync
147143
const BATCH_SIZE: u32 = 10_000;
148144
let mut loaded_count = 0u32;
149145

150-
// Determine the first blockchain height we need to load.
151146
// For checkpoint syncs we start at the checkpoint base; otherwise we skip genesis (already present).
152-
let base_height = if self.is_synced_from_checkpoint() && self.get_sync_base_height() > 0 {
153-
self.get_sync_base_height()
154-
} else {
155-
1
156-
};
157-
158-
let mut current_height = base_height;
147+
let mut current_height = self.get_sync_base_height().max(1);
159148

160149
while current_height <= tip_height {
161150
let end_height = (current_height + BATCH_SIZE - 1).min(tip_height);
@@ -406,7 +395,7 @@ impl<S: StorageManager + Send + Sync + 'static, N: NetworkManager + Send + Sync
406395
Some(hash) => {
407396
// When syncing from a checkpoint, we need to create a proper locator
408397
// that helps the peer understand we want headers AFTER this point
409-
if self.is_synced_from_checkpoint() && self.get_sync_base_height() > 0 {
398+
if self.is_synced_from_checkpoint() {
410399
// For checkpoint sync, only include the checkpoint hash
411400
// Including genesis would allow peers to fall back to sending headers from genesis
412401
// if they don't recognize the checkpoint, which is exactly what we want to avoid
@@ -651,9 +640,8 @@ impl<S: StorageManager + Send + Sync + 'static, N: NetworkManager + Send + Sync
651640

652641
tracing::info!("Preparing header synchronization");
653642
tracing::info!(
654-
"Chain state before prepare_sync: sync_base_height={}, synced_from_checkpoint={}, headers_count={}",
643+
"Chain state before prepare_sync: sync_base_height={}, headers_count={}",
655644
self.get_sync_base_height(),
656-
self.is_synced_from_checkpoint(),
657645
self.chain_state.read().await.headers.len()
658646
);
659647

@@ -679,8 +667,8 @@ impl<S: StorageManager + Send + Sync + 'static, N: NetworkManager + Send + Sync
679667
}
680668
} else {
681669
tracing::info!(
682-
"Not syncing from checkpoint or no tip height. synced_from_checkpoint={}, current_tip_height={:?}",
683-
self.is_synced_from_checkpoint(),
670+
"Not syncing from checkpoint or no tip height. sync_base_height={}, current_tip_height={:?}",
671+
self.get_sync_base_height(),
684672
current_tip_height
685673
);
686674
current_tip_height
@@ -769,12 +757,7 @@ impl<S: StorageManager + Send + Sync + 'static, N: NetworkManager + Send + Sync
769757
} else {
770758
// Get the current tip hash from storage
771759
// When syncing from checkpoint, the storage height is different from effective height
772-
let storage_height = if self.is_synced_from_checkpoint() {
773-
// The actual storage height is effective_height - sync_base_height
774-
height.saturating_sub(self.get_sync_base_height())
775-
} else {
776-
height
777-
};
760+
let storage_height = height.saturating_sub(self.get_sync_base_height());
778761

779762
let tip_header = storage.get_header(storage_height).await.map_err(|e| {
780763
SyncError::Storage(format!(
@@ -855,7 +838,7 @@ impl<S: StorageManager + Send + Sync + 'static, N: NetworkManager + Send + Sync
855838
let recovery_base_hash = match current_tip_height {
856839
None => {
857840
// No headers in storage - check if we're syncing from a checkpoint
858-
if self.is_synced_from_checkpoint() && self.get_sync_base_height() > 0 {
841+
if self.is_synced_from_checkpoint() {
859842
// Use the checkpoint hash from chain state
860843
if !self.chain_state.read().await.headers.is_empty() {
861844
let checkpoint_hash =
@@ -1038,27 +1021,13 @@ impl<S: StorageManager + Send + Sync + 'static, N: NetworkManager + Send + Sync
10381021

10391022
/// Whether we're syncing from a checkpoint
10401023
pub fn is_synced_from_checkpoint(&self) -> bool {
1041-
self.cached_synced_from_checkpoint
1024+
self.cached_sync_base_height > 0
10421025
}
10431026

10441027
/// Update cached flags and totals based on an external state snapshot
1045-
pub fn update_cached_from_state_snapshot(
1046-
&mut self,
1047-
synced_from_checkpoint: bool,
1048-
sync_base_height: u32,
1049-
headers_len: u32,
1050-
) {
1051-
self.cached_synced_from_checkpoint = synced_from_checkpoint;
1028+
pub fn update_cached_from_state_snapshot(&mut self, sync_base_height: u32, headers_len: u32) {
10521029
self.cached_sync_base_height = sync_base_height;
10531030
// Absolute blockchain tip height = base + headers_len - 1 (if any headers exist)
1054-
self.total_headers_synced = if headers_len == 0 {
1055-
if synced_from_checkpoint {
1056-
sync_base_height
1057-
} else {
1058-
0
1059-
}
1060-
} else {
1061-
sync_base_height.saturating_add(headers_len).saturating_sub(1)
1062-
};
1031+
self.total_headers_synced = sync_base_height.saturating_add(headers_len).saturating_sub(1);
10631032
}
10641033
}

0 commit comments

Comments
 (0)