|
6 | 6 | use dashcore::{hash_types::FilterHeader, network::message_filter::CFHeaders, BlockHash}; |
7 | 7 | use dashcore_hashes::{sha256d, Hash}; |
8 | 8 | use std::collections::{HashMap, HashSet, VecDeque}; |
| 9 | +use tokio::sync::Mutex; |
9 | 10 |
|
10 | 11 | use crate::client::ClientConfig; |
11 | 12 | use crate::error::{SyncError, SyncResult}; |
12 | 13 | use crate::network::NetworkManager; |
| 14 | +use crate::storage::metadata_keys::CHECKPOINT_PREV_FILTER_HEADER_KEY; |
13 | 15 | use crate::storage::StorageManager; |
14 | 16 | use crate::types::SharedFilterHeights; |
15 | 17 |
|
16 | 18 | // Import types and constants from the types module |
17 | 19 | use super::types::*; |
18 | 20 |
|
| 21 | +#[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| 22 | +pub(super) struct TrustedCheckpointFilterHeader { |
| 23 | + pub(super) height: u32, |
| 24 | + pub(super) header: FilterHeader, |
| 25 | +} |
| 26 | + |
| 27 | +impl TrustedCheckpointFilterHeader { |
| 28 | + fn to_bytes(self) -> [u8; 36] { |
| 29 | + let mut buf = [0u8; 36]; |
| 30 | + buf[..4].copy_from_slice(&self.height.to_le_bytes()); |
| 31 | + buf[4..].copy_from_slice(self.header.as_byte_array()); |
| 32 | + buf |
| 33 | + } |
| 34 | + |
| 35 | + fn from_bytes(bytes: &[u8]) -> Option<Self> { |
| 36 | + if bytes.len() != 36 { |
| 37 | + return None; |
| 38 | + } |
| 39 | + |
| 40 | + let mut height_bytes = [0u8; 4]; |
| 41 | + height_bytes.copy_from_slice(&bytes[..4]); |
| 42 | + let height = u32::from_le_bytes(height_bytes); |
| 43 | + |
| 44 | + let mut header_bytes = [0u8; 32]; |
| 45 | + header_bytes.copy_from_slice(&bytes[4..36]); |
| 46 | + |
| 47 | + Some(Self { |
| 48 | + height, |
| 49 | + header: FilterHeader::from_byte_array(header_bytes), |
| 50 | + }) |
| 51 | + } |
| 52 | +} |
| 53 | + |
19 | 54 | /// Manages BIP157 compact block filter synchronization. |
20 | 55 | /// |
21 | 56 | /// # Generic Parameters |
@@ -102,6 +137,8 @@ pub struct FilterSyncManager<S: StorageManager, N: NetworkManager> { |
102 | 137 | pub(super) max_concurrent_cfheader_requests: usize, |
103 | 138 | /// Timeout for CFHeaders requests |
104 | 139 | pub(super) cfheader_request_timeout: std::time::Duration, |
| 140 | + /// Trusted predecessor filter header for the configured checkpoint base |
| 141 | + checkpoint_prev_filter_header: Mutex<Option<TrustedCheckpointFilterHeader>>, |
105 | 142 | } |
106 | 143 |
|
107 | 144 | impl<S: StorageManager + Send + Sync + 'static, N: NetworkManager + Send + Sync + 'static> |
@@ -148,6 +185,7 @@ impl<S: StorageManager + Send + Sync + 'static, N: NetworkManager + Send + Sync |
148 | 185 | cfheader_request_timeout: std::time::Duration::from_secs( |
149 | 186 | config.cfheaders_request_timeout_secs, |
150 | 187 | ), |
| 188 | + checkpoint_prev_filter_header: Mutex::new(None), |
151 | 189 | _phantom_s: std::marker::PhantomData, |
152 | 190 | _phantom_n: std::marker::PhantomData, |
153 | 191 | } |
@@ -264,6 +302,54 @@ impl<S: StorageManager + Send + Sync + 'static, N: NetworkManager + Send + Sync |
264 | 302 | Ok(new_filter_headers) |
265 | 303 | } |
266 | 304 |
|
| 305 | + pub(super) async fn persist_checkpoint_prev_filter_header( |
| 306 | + &self, |
| 307 | + storage: &mut S, |
| 308 | + header: TrustedCheckpointFilterHeader, |
| 309 | + ) -> SyncResult<()> { |
| 310 | + storage |
| 311 | + .store_metadata(CHECKPOINT_PREV_FILTER_HEADER_KEY, &header.to_bytes()) |
| 312 | + .await |
| 313 | + .map_err(|e| { |
| 314 | + SyncError::Storage(format!( |
| 315 | + "Failed to persist checkpoint predecessor filter header: {}", |
| 316 | + e |
| 317 | + )) |
| 318 | + })?; |
| 319 | + |
| 320 | + let mut guard = self.checkpoint_prev_filter_header.lock().await; |
| 321 | + *guard = Some(header); |
| 322 | + Ok(()) |
| 323 | + } |
| 324 | + |
| 325 | + pub(super) async fn load_checkpoint_prev_filter_header( |
| 326 | + &self, |
| 327 | + storage: &S, |
| 328 | + ) -> SyncResult<Option<TrustedCheckpointFilterHeader>> { |
| 329 | + let mut guard = self.checkpoint_prev_filter_header.lock().await; |
| 330 | + if guard.is_none() { |
| 331 | + if let Some(bytes) = |
| 332 | + storage.load_metadata(CHECKPOINT_PREV_FILTER_HEADER_KEY).await.map_err(|e| { |
| 333 | + SyncError::Storage(format!( |
| 334 | + "Failed to load checkpoint predecessor filter header: {}", |
| 335 | + e |
| 336 | + )) |
| 337 | + })? |
| 338 | + { |
| 339 | + if let Some(record) = TrustedCheckpointFilterHeader::from_bytes(&bytes) { |
| 340 | + *guard = Some(record); |
| 341 | + } else { |
| 342 | + tracing::warn!( |
| 343 | + "Stored checkpoint predecessor filter header has unexpected format ({} bytes)", |
| 344 | + bytes.len() |
| 345 | + ); |
| 346 | + } |
| 347 | + } |
| 348 | + } |
| 349 | + |
| 350 | + Ok(*guard) |
| 351 | + } |
| 352 | + |
267 | 353 | /// Handle overlapping filter headers by skipping already processed ones. |
268 | 354 | pub fn has_pending_downloads(&self) -> bool { |
269 | 355 | !self.pending_block_downloads.is_empty() || !self.downloading_blocks.is_empty() |
|
0 commit comments