Skip to content

Commit 408b997

Browse files
refactor(dash-spv): simplify sync logic following dash-evo-tool pattern
Simplify synchronization architecture by adopting the proven dash-evo-tool approach: **Discovery simplification (discovery.rs):** - Remove complex engine-driven discovery logic - Keep minimal QRInfoRequest struct for compatibility - Focus on direct dash-evo-tool pattern with single QRInfo requests **Masternode sync simplification (masternodes.rs):** - Implement simplified MasternodeSyncManager following dash-evo-tool pattern - Use direct fetch_rotated_quorum_info approach instead of complex batching - Add simple caches for mnlist_diffs and qr_infos matching dash-evo-tool - Remove complex correlation and parallel processing logic **Module organization (mod.rs):** - Update sync module to focus on sequential strategy - Deprecate legacy SyncManager in favor of SequentialSyncManager - Remove references to deleted parallel sync modules **Sequential sync updates (sequential/mod.rs):** - Continue using proven sequential approach - Maintain compatibility while simplifying underlying implementation This refactoring removes over-engineered complexity in favor of the proven, simple approach used by dash-evo-tool for reliable QRInfo synchronization. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent ac30563 commit 408b997

File tree

4 files changed

+540
-1401
lines changed

4 files changed

+540
-1401
lines changed

dash-spv/src/sync/discovery.rs

Lines changed: 22 additions & 264 deletions
Original file line numberDiff line numberDiff line change
@@ -1,221 +1,11 @@
1-
//! Engine-driven discovery of missing masternode data.
1+
//! Simplified discovery for masternode data following dash-evo-tool approach.
22
//!
3-
//! This module provides intelligent discovery of missing masternode lists
4-
//! using the masternode list engine's built-in methods rather than manual
5-
//! height tracking. It identifies gaps in masternode data and creates
6-
//! optimal QRInfo requests to fill those gaps.
3+
//! Since we use the direct dash-evo-tool pattern with single QRInfo requests,
4+
//! complex discovery logic is not needed.
75
8-
use dashcore::{
9-
BlockHash,
10-
sml::{
11-
llmq_entry_verification::LLMQEntryVerificationStatus,
12-
llmq_type::LLMQType,
13-
masternode_list_engine::MasternodeListEngine,
14-
},
15-
};
16-
use std::collections::{BTreeMap, BTreeSet};
17-
use tracing;
6+
use dashcore::BlockHash;
187

19-
/// Service for discovering missing masternode data using engine methods
20-
#[derive(Debug)]
21-
pub struct MasternodeDiscoveryService {
22-
/// LLMQ types to exclude from discovery (configurable)
23-
excluded_quorum_types: Vec<LLMQType>,
24-
}
25-
26-
impl MasternodeDiscoveryService {
27-
/// Create a new discovery service with default configuration.
28-
pub fn new() -> Self {
29-
Self {
30-
// Exclude types we don't need for SPV
31-
excluded_quorum_types: vec![
32-
LLMQType::Llmqtype60_75, // Too small for meaningful validation
33-
LLMQType::Llmqtype50_60, // Platform-specific, not needed for SPV
34-
],
35-
}
36-
}
37-
38-
/// Discover which masternode lists are missing from the engine.
39-
///
40-
/// This uses the engine's `latest_masternode_list_non_rotating_quorum_hashes`
41-
/// method to identify block hashes where we need masternode lists but don't
42-
/// have them.
43-
pub fn discover_missing_masternode_lists(
44-
&self,
45-
engine: &MasternodeListEngine,
46-
) -> DiscoveryResult {
47-
// Use engine's built-in discovery method
48-
let missing_hashes = engine.latest_masternode_list_non_rotating_quorum_hashes(
49-
&self.excluded_quorum_types,
50-
true // only_return_block_hashes_with_missing_masternode_lists_from_engine
51-
);
52-
53-
tracing::info!("Discovered {} missing masternode lists", missing_hashes.len());
54-
55-
// Convert block hashes to heights using engine's block container
56-
let mut missing_by_height = BTreeMap::new();
57-
for hash in missing_hashes {
58-
if let Some(height) = engine.block_container.get_height(&hash) {
59-
missing_by_height.insert(height, hash);
60-
tracing::debug!("Missing masternode list at height {}: {:x}", height, hash);
61-
} else {
62-
tracing::warn!("Found missing hash {:x} but no height mapping", hash);
63-
}
64-
}
65-
66-
let total_discovered = missing_by_height.len();
67-
let requires_qr_info = !missing_by_height.is_empty();
68-
69-
DiscoveryResult {
70-
missing_by_height,
71-
total_discovered,
72-
requires_qr_info,
73-
}
74-
}
75-
76-
/// Discover rotating quorums that need validation.
77-
///
78-
/// This identifies rotating quorums that either:
79-
/// - Need validation (verification status is Unknown)
80-
/// - Are missing cycle data needed for rotation
81-
pub fn discover_rotating_quorum_needs(
82-
&self,
83-
engine: &MasternodeListEngine,
84-
) -> RotatingQuorumDiscovery {
85-
let rotating_hashes = engine.latest_masternode_list_rotating_quorum_hashes(
86-
&self.excluded_quorum_types
87-
);
88-
89-
let mut needs_validation = Vec::new();
90-
let mut missing_cycle_data = Vec::new();
91-
92-
for hash in rotating_hashes {
93-
if let Some(height) = engine.block_container.get_height(&hash) {
94-
// Check if we have the quorum cycle data
95-
if !engine.rotated_quorums_per_cycle.contains_key(&hash) {
96-
missing_cycle_data.push((height, hash));
97-
}
98-
99-
// Check if quorum needs validation
100-
if let Some(list) = engine.masternode_lists.get(&height) {
101-
for (llmq_type, quorums) in &list.quorums {
102-
if llmq_type.is_rotating_quorum_type() {
103-
for (_, quorum_entry) in quorums {
104-
if quorum_entry.verified == LLMQEntryVerificationStatus::Unknown {
105-
needs_validation.push((height, hash, *llmq_type));
106-
}
107-
}
108-
}
109-
}
110-
}
111-
}
112-
}
113-
114-
RotatingQuorumDiscovery {
115-
needs_validation,
116-
missing_cycle_data,
117-
}
118-
}
119-
120-
/// Create optimal QRInfo requests based on discovery results.
121-
///
122-
/// This groups missing heights into efficient ranges for QRInfo requests,
123-
/// taking into account:
124-
/// - Maximum span for QRInfo requests
125-
/// - Network efficiency (batching nearby heights)
126-
/// - Priority (more recent blocks have higher priority for SPV)
127-
pub fn plan_qr_info_requests(
128-
&self,
129-
discovery: &DiscoveryResult,
130-
max_request_span: u32,
131-
) -> Vec<QRInfoRequest> {
132-
let mut requests = Vec::new();
133-
134-
if discovery.missing_by_height.is_empty() {
135-
return requests;
136-
}
137-
138-
// Group missing heights into ranges for efficient QRInfo requests
139-
let heights: Vec<u32> = discovery.missing_by_height.keys().cloned().collect();
140-
let mut current_range_start = heights[0];
141-
let mut current_range_end = heights[0];
142-
143-
for &height in &heights[1..] {
144-
if height - current_range_end <= max_request_span &&
145-
height - current_range_start <= max_request_span * 3 {
146-
// Extend current range
147-
current_range_end = height;
148-
} else {
149-
// Finalize current range and start new one
150-
requests.push(QRInfoRequest {
151-
base_height: current_range_start.saturating_sub(8), // h-8 for validation
152-
tip_height: current_range_end,
153-
base_hash: discovery.missing_by_height[&current_range_start],
154-
tip_hash: discovery.missing_by_height[&current_range_end],
155-
extra_share: true, // Always request extra validation data
156-
priority: self.calculate_priority(current_range_start, current_range_end),
157-
});
158-
159-
current_range_start = height;
160-
current_range_end = height;
161-
}
162-
}
163-
164-
// Add final range
165-
requests.push(QRInfoRequest {
166-
base_height: current_range_start.saturating_sub(8),
167-
tip_height: current_range_end,
168-
base_hash: discovery.missing_by_height[&current_range_start],
169-
tip_hash: discovery.missing_by_height[&current_range_end],
170-
extra_share: true,
171-
priority: self.calculate_priority(current_range_start, current_range_end),
172-
});
173-
174-
// Sort by priority (most recent first for SPV)
175-
requests.sort_by(|a, b| b.priority.cmp(&a.priority));
176-
177-
tracing::info!(
178-
"Planned {} QRInfo requests covering {} heights",
179-
requests.len(),
180-
discovery.total_discovered
181-
);
182-
183-
requests
184-
}
185-
186-
fn calculate_priority(&self, _start_height: u32, end_height: u32) -> u32 {
187-
// More recent blocks have higher priority for SPV
188-
end_height
189-
}
190-
}
191-
192-
impl Default for MasternodeDiscoveryService {
193-
fn default() -> Self {
194-
Self::new()
195-
}
196-
}
197-
198-
/// Result of masternode list discovery
199-
#[derive(Debug)]
200-
pub struct DiscoveryResult {
201-
/// Map of heights to block hashes where masternode lists are missing
202-
pub missing_by_height: BTreeMap<u32, BlockHash>,
203-
/// Total number of missing masternode lists discovered
204-
pub total_discovered: usize,
205-
/// Whether QRInfo requests are needed
206-
pub requires_qr_info: bool,
207-
}
208-
209-
/// Result of rotating quorum discovery
210-
#[derive(Debug)]
211-
pub struct RotatingQuorumDiscovery {
212-
/// Quorums that need validation: (height, block_hash, llmq_type)
213-
pub needs_validation: Vec<(u32, BlockHash, LLMQType)>,
214-
/// Heights missing quorum cycle data: (height, block_hash)
215-
pub missing_cycle_data: Vec<(u32, BlockHash)>,
216-
}
217-
218-
/// A planned QRInfo request
8+
/// Simplified request for QRInfo data (kept for compatibility)
2199
#[derive(Debug, Clone, PartialEq, Eq)]
22010
pub struct QRInfoRequest {
22111
/// Base block height for the request
@@ -226,57 +16,25 @@ pub struct QRInfoRequest {
22616
pub base_hash: BlockHash,
22717
/// Tip block hash
22818
pub tip_hash: BlockHash,
229-
/// Whether to request extra share data
19+
/// Whether to request extra validation data
23020
pub extra_share: bool,
231-
/// Priority of this request (higher = more important)
232-
pub priority: u32,
23321
}
23422

235-
#[cfg(test)]
236-
mod tests {
237-
use super::*;
238-
use dashcore::Network;
239-
240-
fn create_test_engine() -> MasternodeListEngine {
241-
MasternodeListEngine::default_for_network(Network::Testnet)
242-
}
243-
244-
#[test]
245-
fn test_discovery_with_no_missing_lists() {
246-
let engine = create_test_engine();
247-
let discovery_service = MasternodeDiscoveryService::new();
248-
249-
let result = discovery_service.discover_missing_masternode_lists(&engine);
250-
251-
assert_eq!(result.total_discovered, 0);
252-
assert!(!result.requires_qr_info);
253-
assert!(result.missing_by_height.is_empty());
254-
}
255-
256-
#[test]
257-
fn test_qr_info_request_planning() {
258-
let discovery_service = MasternodeDiscoveryService::new();
259-
260-
// Create discovery result with scattered missing heights
261-
let mut missing_by_height = BTreeMap::new();
262-
missing_by_height.insert(1000, BlockHash::all_zeros());
263-
missing_by_height.insert(1001, BlockHash::all_zeros());
264-
missing_by_height.insert(1002, BlockHash::all_zeros());
265-
missing_by_height.insert(1100, BlockHash::all_zeros()); // Gap
266-
missing_by_height.insert(1200, BlockHash::all_zeros()); // Another gap
267-
268-
let discovery = DiscoveryResult {
269-
missing_by_height,
270-
total_discovered: 5,
271-
requires_qr_info: true,
272-
};
273-
274-
let requests = discovery_service.plan_qr_info_requests(&discovery, 50);
275-
276-
// Should group 1000-1002 together, 1100 separate, 1200 separate
277-
assert_eq!(requests.len(), 3);
278-
279-
// Check priorities (higher for more recent)
280-
assert!(requests[0].priority >= requests[1].priority);
23+
impl QRInfoRequest {
24+
/// Create a new QRInfo request
25+
pub fn new(
26+
base_height: u32,
27+
tip_height: u32,
28+
base_hash: BlockHash,
29+
tip_hash: BlockHash,
30+
extra_share: bool,
31+
) -> Self {
32+
Self {
33+
base_height,
34+
tip_height,
35+
base_hash,
36+
tip_hash,
37+
extra_share,
38+
}
28139
}
28240
}

0 commit comments

Comments
 (0)