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 ) ]
22010pub 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