Skip to content

Commit df7628b

Browse files
committed
Stop loading headers twice into ChainState::headers
We currently load the headers twice into the header vector in `HeaderSyncManager::load_headers_from_storage`: - First1. within `storage.load_chain_state` - And then within `load_headers_from_storage` itself further down ``` 2025-12-04T16:05:36.717779Z INFO 🚀 Starting sync from checkpoint at height 1900000 instead of genesis (requested start height: 2200000) 2025-12-04T16:05:36.727966Z INFO ✅ Loaded 2 headers into HeaderSyncManager in 0.00s (1129 headers/sec) .... 2025-12-05T04:31:15.980982Z ERROR Sequential sync manager error handling message: Validation error: Block at height 2300000 does not match checkpoint ``` ``` 2025-12-04T16:09:37.959349Z INFO 🚀 Starting sync from checkpoint at height 1900000 instead of genesis (requested start height: 2200000) 2025-12-04T16:09:37.966703Z INFO ✅ Loaded 1 headers into HeaderSyncManager in 0.00s (862 headers/sec) ´´´
1 parent 8e94e50 commit df7628b

File tree

2 files changed

+11
-93
lines changed

2 files changed

+11
-93
lines changed

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

Lines changed: 9 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -102,116 +102,37 @@ impl<S: StorageManager + Send + Sync + 'static, N: NetworkManager + Send + Sync
102102

103103
/// Load headers from storage into the chain state
104104
pub async fn load_headers_from_storage(&mut self, storage: &S) -> SyncResult<u32> {
105+
let start_time = std::time::Instant::now();
106+
let mut loaded_count = 0;
107+
let mut tip_height = 0;
105108
// First, try to load the persisted chain state which may contain sync_base_height
106109
if let Ok(Some(stored_chain_state)) = storage.load_chain_state().await {
107110
tracing::info!(
108111
"Loaded chain state from storage with sync_base_height: {}",
109112
stored_chain_state.sync_base_height,
110113
);
111-
// Update our chain state with the loaded one to preserve sync_base_height
114+
// Update our chain state with the loaded one
112115
{
116+
loaded_count = stored_chain_state.headers.len();
117+
tip_height = stored_chain_state.tip_height();
113118
self.cached_sync_base_height = stored_chain_state.sync_base_height;
114119
let mut cs = self.chain_state.write().await;
115120
*cs = stored_chain_state;
116121
}
117122
}
118123

119-
// Get the current tip height from storage
120-
let tip_height = storage
121-
.get_tip_height()
122-
.await
123-
.map_err(|e| SyncError::Storage(format!("Failed to get tip height: {}", e)))?;
124-
125-
let Some(tip_height) = tip_height else {
126-
tracing::debug!("No headers found in storage");
127-
// If we're syncing from a checkpoint, this is expected
128-
if self.is_synced_from_checkpoint() {
129-
tracing::info!("No headers in storage for checkpoint sync - this is expected");
130-
return Ok(0);
131-
}
132-
return Ok(0);
133-
};
134-
135-
if tip_height == 0 && !self.is_synced_from_checkpoint() {
136-
tracing::debug!("Only genesis block in storage");
137-
return Ok(0);
138-
}
139-
140-
tracing::info!("Loading {} headers from storage into HeaderSyncManager", tip_height);
141-
let start_time = std::time::Instant::now();
142-
143-
// Load headers in batches
144-
const BATCH_SIZE: u32 = 10_000;
145-
let mut loaded_count = 0u32;
146-
147-
// For checkpoint syncs we start at the checkpoint base; otherwise we skip genesis (already present).
148-
let mut current_height = self.get_sync_base_height().max(1);
149-
150-
while current_height <= tip_height {
151-
let end_height = (current_height + BATCH_SIZE - 1).min(tip_height);
152-
153-
// Load batch from storage
154-
let headers_result = storage.load_headers(current_height..end_height + 1).await;
155-
156-
match headers_result {
157-
Ok(headers) if !headers.is_empty() => {
158-
// Add headers to chain state
159-
{
160-
let mut cs = self.chain_state.write().await;
161-
for header in headers {
162-
cs.add_header(header);
163-
loaded_count += 1;
164-
}
165-
}
166-
}
167-
Ok(_) => {
168-
// Empty headers - this can happen for checkpoint sync with minimal headers
169-
tracing::debug!(
170-
"No headers found for range {}..{} - continuing",
171-
current_height,
172-
end_height + 1
173-
);
174-
// Break out of the loop since we've reached the end of available headers
175-
break;
176-
}
177-
Err(e) => {
178-
// For checkpoint sync with only 1 header stored, this is expected
179-
if self.is_synced_from_checkpoint() && loaded_count == 0 && tip_height == 0 {
180-
tracing::info!(
181-
"No additional headers to load for checkpoint sync - this is expected"
182-
);
183-
return Ok(0);
184-
}
185-
return Err(SyncError::Storage(format!("Failed to load headers: {}", e)));
186-
}
187-
}
188-
189-
// Progress logging
190-
if loaded_count.is_multiple_of(50_000) || loaded_count == tip_height {
191-
let elapsed = start_time.elapsed();
192-
let headers_per_sec = loaded_count as f64 / elapsed.as_secs_f64();
193-
tracing::info!(
194-
"Loaded {}/{} headers ({:.0} headers/sec)",
195-
loaded_count,
196-
tip_height,
197-
headers_per_sec
198-
);
199-
}
200-
201-
current_height = end_height + 1;
202-
}
203-
204124
self.total_headers_synced = tip_height;
205125

206126
let elapsed = start_time.elapsed();
207127
tracing::info!(
208-
"✅ Loaded {} headers into HeaderSyncManager in {:.2}s ({:.0} headers/sec)",
128+
"✅ Loaded {} headers for tip height {} into HeaderSyncManager in {:.2}s ({:.0} headers/sec)",
209129
loaded_count,
130+
tip_height,
210131
elapsed.as_secs_f64(),
211132
loaded_count as f64 / elapsed.as_secs_f64()
212133
);
213134

214-
Ok(loaded_count)
135+
Ok(loaded_count as u32)
215136
}
216137

217138
/// Handle a Headers message

dash-spv/tests/header_sync_test.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ use dashcore_hashes::Hash;
1414
use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo;
1515
use key_wallet_manager::wallet_manager::WalletManager;
1616
use log::{debug, info};
17-
use test_case::test_case;
1817
use std::sync::Arc;
1918
use tempfile::TempDir;
19+
use test_case::test_case;
2020
use tokio::sync::RwLock;
2121

2222
#[tokio::test]
@@ -343,10 +343,7 @@ async fn test_header_storage_consistency() {
343343
#[test_case(170000, 1 ; "checkpoint_1_block")]
344344
#[test_case(12345, 60000 ; "checkpoint_60000_blocks")]
345345
#[tokio::test]
346-
async fn test_load_headers_from_storage(
347-
sync_base_height: u32,
348-
header_count: usize,
349-
) {
346+
async fn test_load_headers_from_storage(sync_base_height: u32, header_count: usize) {
350347
// Setup: Create storage with 100 headers
351348
let temp_dir = TempDir::new().expect("Failed to create temp dir");
352349
let mut storage = DiskStorageManager::new(temp_dir.path().to_path_buf())

0 commit comments

Comments
 (0)