From 426bcc6260221c2af0a5cf54bd5ef9a2be15efc4 Mon Sep 17 00:00:00 2001 From: xdustinface Date: Sat, 11 Apr 2026 00:47:52 +1000 Subject: [PATCH 1/2] feat: add `QRInfoFeedResult` and return it from `feed_qr_info` For now just add logging for the result. This will also be used more in follow up PRs. --- dash-spv/src/sync/masternodes/sync_manager.rs | 82 +++++++++++-------- dash/src/sml/masternode_list_engine/mod.rs | 43 +++++++++- 2 files changed, 89 insertions(+), 36 deletions(-) diff --git a/dash-spv/src/sync/masternodes/sync_manager.rs b/dash-spv/src/sync/masternodes/sync_manager.rs index ffc15e7b3..0ea8ba30f 100644 --- a/dash-spv/src/sync/masternodes/sync_manager.rs +++ b/dash-spv/src/sync/masternodes/sync_manager.rs @@ -235,43 +235,48 @@ impl SyncManager for MasternodesManager { tracing::info!("Fed {} block heights to engine", fed); // Feed QRInfo to engine first to populate masternode lists - if let Err(e) = engine.feed_qr_info(qr_info.clone(), true, true) { - // Check if this is a tip ChainLock error (h - 0 means the tip block) - // The QRInfo response always includes `mn_list_diff_tip` which is the current - // chain tip. If the tip was just mined, the ChainLock hasn't propagated yet. - let is_tip_chainlock_error = matches!( - e, - QuorumValidationError::RequiredRotatedChainLockSigNotPresent(0, _) - ); - - if is_tip_chainlock_error { - self.sync_state.qrinfo_retry_count += 1; + let summary = match engine.feed_qr_info(qr_info.clone(), true, true) { + Ok(summary) => summary, + Err(e) => { + // Check if this is a tip ChainLock error (h - 0 means the tip block) + // The QRInfo response always includes `mn_list_diff_tip` which is the + // current chain tip. If the tip was just mined, the ChainLock hasn't + // propagated yet. + let is_tip_chainlock_error = matches!( + e, + QuorumValidationError::RequiredRotatedChainLockSigNotPresent(0, _) + ); - if self.sync_state.qrinfo_retry_count <= MAX_RETRY_ATTEMPTS { - tracing::info!( - "ChainLock not yet available for tip, scheduling retry {}/{} in {}s", - self.sync_state.qrinfo_retry_count, - MAX_RETRY_ATTEMPTS, - CHAINLOCK_RETRY_DELAY_SECS - ); - // Schedule a delayed retry - the tick handler will trigger it - self.sync_state.chainlock_retry_after = Some( - Instant::now() + Duration::from_secs(CHAINLOCK_RETRY_DELAY_SECS), - ); - drop(engine); - self.set_state(SyncState::Syncing); - return Ok(vec![]); + if is_tip_chainlock_error { + self.sync_state.qrinfo_retry_count += 1; + + if self.sync_state.qrinfo_retry_count <= MAX_RETRY_ATTEMPTS { + tracing::info!( + "ChainLock not yet available for tip, scheduling retry {}/{} in {}s", + self.sync_state.qrinfo_retry_count, + MAX_RETRY_ATTEMPTS, + CHAINLOCK_RETRY_DELAY_SECS + ); + // Schedule a delayed retry - the tick handler will trigger it + self.sync_state.chainlock_retry_after = Some( + Instant::now() + + Duration::from_secs(CHAINLOCK_RETRY_DELAY_SECS), + ); + drop(engine); + self.set_state(SyncState::Syncing); + return Ok(vec![]); + } } - } - // For other errors or max retries reached, fail - tracing::error!( - "QRInfo failed after {} retries: {}", - self.sync_state.qrinfo_retry_count, - e - ); - return Err(SyncError::MasternodeSyncFailed(e.to_string())); - } + // For other errors or max retries reached, fail + tracing::error!( + "QRInfo failed after {} retries: {}", + self.sync_state.qrinfo_retry_count, + e + ); + return Err(SyncError::MasternodeSyncFailed(e.to_string())); + } + }; // Populate known_mn_list_heights from engine after QRInfo processing self.sync_state.known_mn_list_heights = @@ -296,6 +301,15 @@ impl SyncManager for MasternodesManager { drop(engine); drop(storage); + if let Some(summary) = summary { + tracing::info!( + "QRInfo processed: stored_cycle_height={:?}, rotated_quorum_count={}, freshly_validated_count={}", + summary.stored_cycle_height, + summary.rotated_quorum_count, + summary.freshly_validated_count, + ); + } + // Queue and send MnListDiff requests via pipeline self.sync_state.mnlistdiff_pipeline.queue_requests(request_pairs); self.sync_state.mnlistdiff_pipeline.send_pending(requests)?; diff --git a/dash/src/sml/masternode_list_engine/mod.rs b/dash/src/sml/masternode_list_engine/mod.rs index 064b58d68..1bdcb6062 100644 --- a/dash/src/sml/masternode_list_engine/mod.rs +++ b/dash/src/sml/masternode_list_engine/mod.rs @@ -37,6 +37,25 @@ use serde::{Deserialize, Serialize}; /// The mnListDiffH in QRInfo is at (cycle_height - WORK_DIFF_DEPTH), not at the cycle boundary itself pub const WORK_DIFF_DEPTH: u32 = 8; +/// Callers can use this to log what happened during the call, or to decide whether a +/// rotation cycle has been freshly validated this round. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct QRInfoFeedResult { + /// Total number of rotated quorums in `last_commitment_per_index` for this QRInfo. + pub rotated_quorum_count: usize, + /// Rotated quorums that went through the fresh-validation path this call, using + /// the four rotation CL signatures supplied by this QRInfo. + pub freshly_validated_count: usize, + /// Height of the cycle under which rotated quorums were stored in + /// `rotated_quorums_per_cycle`. `None` when nothing was stored, either + /// because `last_commitment_per_index` was empty or its first entry's + /// `quorum_hash` could not be resolved to a height. + /// + /// This is the first entry's `quorum_hash` height, which carries the + /// previous cycle when the current cycle's DKG has not completed yet. + pub stored_cycle_height: Option, +} + #[derive(Clone, Eq, PartialEq, Default)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "bincode", derive(Encode, Decode))] @@ -504,7 +523,7 @@ impl MasternodeListEngine { qr_info: QRInfo, verify_tip_non_rotated_quorums: bool, verify_rotated_quorums: bool, - ) -> Result<(), QuorumValidationError> { + ) -> Result, QuorumValidationError> { #[allow(unused_variables)] let QRInfo { quorum_snapshot_at_h_minus_c, @@ -552,6 +571,14 @@ impl MasternodeListEngine { .map(|quorum_entry| quorum_entry.llmq_type) .unwrap_or(self.network.isd_llmq_type()); + #[cfg(feature = "quorum_validation")] + let stored_cycle_height = last_commitment_per_index + .first() + .and_then(|q| self.block_container.get_height(&q.quorum_hash)); + #[cfg(feature = "quorum_validation")] + let rotated_quorum_count = last_commitment_per_index.len(); + #[cfg(feature = "quorum_validation")] + let mut freshly_validated_count: usize = 0; if let Some((quorum_snapshot_at_h_minus_4c, mn_list_diff_at_h_minus_4c)) = quorum_snapshot_and_mn_list_diff_at_h_minus_4c { @@ -597,6 +624,7 @@ impl MasternodeListEngine { { Ok(qualified_quorum_entry) } else { + freshly_validated_count += 1; let sigm2 = maybe_sigm2.ok_or( QuorumValidationError::RequiredRotatedChainLockSigNotPresent( 3, @@ -795,7 +823,18 @@ impl MasternodeListEngine { )); } - Ok(()) + #[cfg(feature = "quorum_validation")] + { + Ok(Some(QRInfoFeedResult { + rotated_quorum_count, + freshly_validated_count, + stored_cycle_height, + })) + } + #[cfg(not(feature = "quorum_validation"))] + { + Ok(None) + } } /// Applies a masternode list diff to create or update a masternode list. From 103f89014810789817d805253fb17b6a1ced53ac Mon Sep 17 00:00:00 2001 From: xdustinface Date: Fri, 24 Apr 2026 21:42:46 +1000 Subject: [PATCH 2/2] `summary` -> `result` --- dash-spv/src/sync/masternodes/sync_manager.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dash-spv/src/sync/masternodes/sync_manager.rs b/dash-spv/src/sync/masternodes/sync_manager.rs index 0ea8ba30f..92b165f41 100644 --- a/dash-spv/src/sync/masternodes/sync_manager.rs +++ b/dash-spv/src/sync/masternodes/sync_manager.rs @@ -235,8 +235,8 @@ impl SyncManager for MasternodesManager { tracing::info!("Fed {} block heights to engine", fed); // Feed QRInfo to engine first to populate masternode lists - let summary = match engine.feed_qr_info(qr_info.clone(), true, true) { - Ok(summary) => summary, + let result = match engine.feed_qr_info(qr_info.clone(), true, true) { + Ok(result) => result, Err(e) => { // Check if this is a tip ChainLock error (h - 0 means the tip block) // The QRInfo response always includes `mn_list_diff_tip` which is the @@ -301,12 +301,12 @@ impl SyncManager for MasternodesManager { drop(engine); drop(storage); - if let Some(summary) = summary { + if let Some(result) = result { tracing::info!( "QRInfo processed: stored_cycle_height={:?}, rotated_quorum_count={}, freshly_validated_count={}", - summary.stored_cycle_height, - summary.rotated_quorum_count, - summary.freshly_validated_count, + result.stored_cycle_height, + result.rotated_quorum_count, + result.freshly_validated_count, ); }