From 2340750f2a9a10e2364acc9f6ef53250afefcceb Mon Sep 17 00:00:00 2001 From: xdustinface Date: Thu, 27 Nov 2025 01:26:24 +1000 Subject: [PATCH 1/2] feat: Introduce `DashSpvClientInterface` --- dash-spv-ffi/src/client.rs | 14 +- dash-spv-ffi/src/error.rs | 2 + dash-spv/examples/filter_sync.rs | 27 +--- dash-spv/examples/simple_sync.rs | 17 +- dash-spv/examples/spv_with_wallet.rs | 17 +- dash-spv/src/client/interface.rs | 79 ++++++++++ dash-spv/src/client/mod.rs | 1 + dash-spv/src/client/queries.rs | 51 +++--- dash-spv/src/client/sync_coordinator.rs | 200 +++++++++++++++--------- dash-spv/src/error.rs | 6 + dash-spv/src/main.rs | 31 +--- 11 files changed, 269 insertions(+), 176 deletions(-) create mode 100644 dash-spv/src/client/interface.rs diff --git a/dash-spv-ffi/src/client.rs b/dash-spv-ffi/src/client.rs index bcff480f2..8fe0cb7be 100644 --- a/dash-spv-ffi/src/client.rs +++ b/dash-spv-ffi/src/client.rs @@ -886,23 +886,27 @@ pub unsafe extern "C" fn dash_spv_ffi_client_sync_to_tip_with_progress( } } }; + let (_command_sender, command_receiver) = tokio::sync::mpsc::unbounded_channel(); + let run_token = shutdown_token_sync.clone(); let (abort_handle, abort_registration) = AbortHandle::new_pair(); - let mut monitor_future = - Box::pin(Abortable::new(spv_client.monitor_network(), abort_registration)); + let mut run_future = Box::pin(Abortable::new( + spv_client.run(command_receiver, run_token), + abort_registration, + )); let result = tokio::select! { - res = &mut monitor_future => match res { + res = &mut run_future => match res { Ok(inner) => inner, Err(_) => Ok(()), }, _ = shutdown_token_sync.cancelled() => { abort_handle.abort(); - match monitor_future.as_mut().await { + match run_future.as_mut().await { Ok(inner) => inner, Err(_) => Ok(()), } } }; - drop(monitor_future); + drop(run_future); let mut guard = inner.lock().unwrap(); *guard = Some(spv_client); result diff --git a/dash-spv-ffi/src/error.rs b/dash-spv-ffi/src/error.rs index 2d9777164..65bb024dc 100644 --- a/dash-spv-ffi/src/error.rs +++ b/dash-spv-ffi/src/error.rs @@ -52,6 +52,7 @@ pub extern "C" fn dash_spv_ffi_clear_error() { impl From for FFIErrorCode { fn from(err: SpvError) -> Self { match err { + SpvError::ChannelFailure(_, _) => FFIErrorCode::RuntimeError, SpvError::Network(_) => FFIErrorCode::NetworkError, SpvError::Storage(_) => FFIErrorCode::StorageError, SpvError::Validation(_) => FFIErrorCode::ValidationError, @@ -60,6 +61,7 @@ impl From for FFIErrorCode { SpvError::Config(_) => FFIErrorCode::ConfigError, SpvError::Parse(_) => FFIErrorCode::ValidationError, SpvError::Wallet(_) => FFIErrorCode::WalletError, + SpvError::QuorumLookupError(_) => FFIErrorCode::ValidationError, SpvError::General(_) => FFIErrorCode::Unknown, } } diff --git a/dash-spv/examples/filter_sync.rs b/dash-spv/examples/filter_sync.rs index 0f938428d..4f3dd753d 100644 --- a/dash-spv/examples/filter_sync.rs +++ b/dash-spv/examples/filter_sync.rs @@ -8,8 +8,8 @@ use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_manager::wallet_manager::WalletManager; use std::str::FromStr; use std::sync::Arc; -use tokio::signal; use tokio::sync::RwLock; +use tokio_util::sync::CancellationToken; #[tokio::main] async fn main() -> Result<(), Box> { @@ -43,29 +43,12 @@ async fn main() -> Result<(), Box> { println!("Watching address: {:?}", watch_address); // Full sync including filters - let progress = client.sync_to_tip().await?; + client.sync_to_tip().await?; - tokio::select! { - result = client.monitor_network() => { - println!("monitor_network result {:?}", result); - }, - _ = signal::ctrl_c() => { - println!("monitor_network canceled"); - } - } + let (_command_sender, command_receiver) = tokio::sync::mpsc::unbounded_channel(); + let shutdown_token = CancellationToken::new(); - println!("Headers synced: {}", progress.header_height); - println!("Filter headers synced: {}", progress.filter_header_height); - - // Get statistics - let stats = client.stats().await?; - println!("Filter headers downloaded: {}", stats.filter_headers_downloaded); - println!("Filters downloaded: {}", stats.filters_downloaded); - println!("Filter matches found: {}", stats.filters_matched); - println!("Blocks requested: {}", stats.blocks_requested); - - // Stop the client - client.stop().await?; + client.run_until_shutdown(command_receiver, shutdown_token).await?; println!("Done!"); Ok(()) diff --git a/dash-spv/examples/simple_sync.rs b/dash-spv/examples/simple_sync.rs index 547f71717..140920270 100644 --- a/dash-spv/examples/simple_sync.rs +++ b/dash-spv/examples/simple_sync.rs @@ -7,8 +7,8 @@ use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_manager::wallet_manager::WalletManager; use std::sync::Arc; -use tokio::signal; use tokio::sync::RwLock; +use tokio_util::sync::CancellationToken; #[tokio::main] async fn main() -> Result<(), Box> { @@ -48,17 +48,10 @@ async fn main() -> Result<(), Box> { println!("Headers downloaded: {}", stats.headers_downloaded); println!("Bytes received: {}", stats.bytes_received); - tokio::select! { - result = client.monitor_network() => { - println!("monitor_network result {:?}", result); - }, - _ = signal::ctrl_c() => { - println!("monitor_network canceled"); - } - } - - // Stop the client - client.stop().await?; + let (_command_sender, command_receiver) = tokio::sync::mpsc::unbounded_channel(); + let shutdown_token = CancellationToken::new(); + + client.run_until_shutdown(command_receiver, shutdown_token).await?; println!("Done!"); Ok(()) diff --git a/dash-spv/examples/spv_with_wallet.rs b/dash-spv/examples/spv_with_wallet.rs index d33d6a2d7..64699ef88 100644 --- a/dash-spv/examples/spv_with_wallet.rs +++ b/dash-spv/examples/spv_with_wallet.rs @@ -8,8 +8,8 @@ use dash_spv::{ClientConfig, DashSpvClient}; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_manager::wallet_manager::WalletManager; use std::sync::Arc; -use tokio::signal; use tokio::sync::RwLock; +use tokio_util::sync::CancellationToken; #[tokio::main] async fn main() -> Result<(), Box> { @@ -48,18 +48,11 @@ async fn main() -> Result<(), Box> { // - Mempool transactions via process_mempool_transaction() // - Reorgs via handle_reorg() // - Compact filter checks via check_compact_filter() - tokio::select! { - result = client.monitor_network() => { - println!("monitor_network result {:?}", result); - }, - _ = signal::ctrl_c() => { - println!("monitor_network canceled"); - } - } - // Stop the client - println!("Stopping SPV client..."); - client.stop().await?; + let (_command_sender, command_receiver) = tokio::sync::mpsc::unbounded_channel(); + let shutdown_token = CancellationToken::new(); + + client.run_until_shutdown(command_receiver, shutdown_token).await?; println!("Done!"); Ok(()) diff --git a/dash-spv/src/client/interface.rs b/dash-spv/src/client/interface.rs new file mode 100644 index 000000000..1704c88c8 --- /dev/null +++ b/dash-spv/src/client/interface.rs @@ -0,0 +1,79 @@ +use crate::error::SpvError; +use dashcore::sml::llmq_type::LLMQType; +use dashcore::sml::quorum_entry::qualified_quorum_entry::QualifiedQuorumEntry; +use dashcore::QuorumHash; +use std::fmt::Display; +use tokio::sync::{mpsc, oneshot}; + +pub type Result = std::result::Result; + +pub type GetQuorumByHeightResult = Result; + +async fn receive(context: String, receiver: oneshot::Receiver) -> Result { + receiver.await.map_err(|error| SpvError::ChannelFailure(context, error.to_string())) +} + +pub enum DashSpvClientCommand { + GetQuorumByHeight { + height: u32, + quorum_type: LLMQType, + quorum_hash: QuorumHash, + sender: oneshot::Sender, + }, +} + +impl DashSpvClientCommand { + pub async fn send( + self, + context: String, + sender: mpsc::UnboundedSender, + ) -> Result<()> { + sender.send(self).map_err(|error| SpvError::ChannelFailure(context, error.to_string()))?; + Ok(()) + } +} + +impl Display for DashSpvClientCommand { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let str = match self { + DashSpvClientCommand::GetQuorumByHeight { + height, + quorum_type, + quorum_hash, + sender: _, + } => format!("GetQuorumByHeight({height}, {quorum_type}, {quorum_hash})"), + }; + write!(f, "{}", str) + } +} + +#[derive(Clone)] +pub struct DashSpvClientInterface { + pub command_sender: mpsc::UnboundedSender, +} + +impl DashSpvClientInterface { + pub fn new(command_sender: mpsc::UnboundedSender) -> Self { + Self { + command_sender, + } + } + + pub async fn get_quorum_by_height( + &self, + height: u32, + quorum_type: LLMQType, + quorum_hash: QuorumHash, + ) -> GetQuorumByHeightResult { + let (sender, receiver) = oneshot::channel(); + let command = DashSpvClientCommand::GetQuorumByHeight { + height, + quorum_type, + quorum_hash, + sender, + }; + let context = command.to_string(); + command.send(context.clone(), self.command_sender.clone()).await?; + receive(context, receiver).await? + } +} diff --git a/dash-spv/src/client/mod.rs b/dash-spv/src/client/mod.rs index 1e8c2c0a9..83336fcae 100644 --- a/dash-spv/src/client/mod.rs +++ b/dash-spv/src/client/mod.rs @@ -37,6 +37,7 @@ pub mod block_processor; pub mod config; pub mod filter_sync; +pub mod interface; pub mod message_handler; pub mod status_display; diff --git a/dash-spv/src/client/queries.rs b/dash-spv/src/client/queries.rs index 8e21f796c..bb0be8c3b 100644 --- a/dash-spv/src/client/queries.rs +++ b/dash-spv/src/client/queries.rs @@ -10,9 +10,11 @@ use crate::error::{Result, SpvError}; use crate::network::NetworkManager; use crate::storage::StorageManager; use crate::types::AddressBalance; +use dashcore::sml::llmq_type::LLMQType; use dashcore::sml::masternode_list::MasternodeList; use dashcore::sml::masternode_list_engine::MasternodeListEngine; use dashcore::sml::quorum_entry::qualified_quorum_entry::QualifiedQuorumEntry; +use dashcore::QuorumHash; use key_wallet_manager::wallet_interface::WalletInterface; use super::DashSpvClient; @@ -73,27 +75,15 @@ impl< pub fn get_quorum_at_height( &self, height: u32, - quorum_type: u8, - quorum_hash: &[u8; 32], - ) -> Option<&QualifiedQuorumEntry> { - use dashcore::sml::llmq_type::LLMQType; - use dashcore::QuorumHash; - use dashcore_hashes::Hash; - - let llmq_type: LLMQType = LLMQType::from(quorum_type); - if llmq_type == LLMQType::LlmqtypeUnknown { - tracing::warn!("Invalid quorum type {} requested at height {}", quorum_type, height); - return None; - }; - - let qhash = QuorumHash::from_byte_array(*quorum_hash); - + quorum_type: LLMQType, + quorum_hash: QuorumHash, + ) -> Result { // First check if we have the masternode list at this height match self.get_masternode_list_at_height(height) { Some(ml) => { // We have the masternode list, now look for the quorum - match ml.quorums.get(&llmq_type) { - Some(quorums) => match quorums.get(&qhash) { + match ml.quorums.get(&quorum_type) { + Some(quorums) => match quorums.get(&quorum_hash) { Some(quorum) => { tracing::debug!( "Found quorum type {} at height {} with hash {}", @@ -101,17 +91,16 @@ impl< height, hex::encode(quorum_hash) ); - Some(quorum) + Ok(quorum.clone()) } None => { - tracing::warn!( - "Quorum not found: type {} at height {} with hash {} (masternode list exists with {} quorums of this type)", - quorum_type, - height, - hex::encode(quorum_hash), - quorums.len() - ); - None + let message = format!("Quorum not found: type {} at height {} with hash {} (masternode list exists with {} quorums of this type)", + quorum_type, + height, + hex::encode(quorum_hash), + quorums.len()); + tracing::warn!(message); + Err(SpvError::QuorumLookupError(message)) } }, None => { @@ -120,7 +109,10 @@ impl< quorum_type, height ); - None + Err(SpvError::QuorumLookupError(format!( + "No quorums of type {} found at height {}", + quorum_type, height + ))) } } } @@ -129,7 +121,10 @@ impl< "No masternode list found at height {} - cannot retrieve quorum", height ); - None + Err(SpvError::QuorumLookupError(format!( + "No masternode list found at height {}", + height + ))) } } } diff --git a/dash-spv/src/client/sync_coordinator.rs b/dash-spv/src/client/sync_coordinator.rs index edd043c1a..4fc16edc3 100644 --- a/dash-spv/src/client/sync_coordinator.rs +++ b/dash-spv/src/client/sync_coordinator.rs @@ -11,15 +11,17 @@ //! This is the largest module as it handles all coordination between network, //! storage, and the sync manager. -use std::time::{Duration, Instant, SystemTime}; - use super::{BlockProcessingTask, DashSpvClient, MessageHandler}; +use crate::client::interface::DashSpvClientCommand; use crate::error::{Result, SpvError}; use crate::network::constants::MESSAGE_RECEIVE_TIMEOUT; use crate::network::NetworkManager; use crate::storage::StorageManager; use crate::types::{DetailedSyncProgress, SyncProgress}; use key_wallet_manager::wallet_interface::WalletInterface; +use std::time::{Duration, Instant, SystemTime}; +use tokio::sync::mpsc::UnboundedReceiver; +use tokio_util::sync::CancellationToken; impl< W: WalletInterface + Send + Sync + 'static, @@ -66,7 +68,11 @@ impl< /// /// This is the sole network message receiver to prevent race conditions. /// All sync operations coordinate through this monitoring loop. - pub async fn monitor_network(&mut self) -> Result<()> { + pub async fn run( + &mut self, + mut receiver: UnboundedReceiver, + token: CancellationToken, + ) -> Result<()> { let running = self.running.read().await; if !*running { return Err(SpvError::Config("Client not running".to_string())); @@ -475,82 +481,88 @@ impl< last_chainlock_validation_check = Instant::now(); } - // Handle network messages with timeout for responsiveness - match tokio::time::timeout(MESSAGE_RECEIVE_TIMEOUT, self.network.receive_message()) - .await - { - Ok(msg_result) => match msg_result { - Ok(Some(message)) => { - // Wrap message handling in comprehensive error handling - match self.handle_network_message(message).await { - Ok(_) => { - // Message handled successfully - } - Err(e) => { - tracing::error!("Error handling network message: {}", e); - - // Categorize error severity - match &e { - SpvError::Network(_) => { - tracing::warn!("Network error during message handling - may recover automatically"); - } - SpvError::Storage(_) => { - tracing::error!("Storage error during message handling - this may affect data consistency"); - } - SpvError::Validation(_) => { - tracing::warn!("Validation error during message handling - message rejected"); - } - _ => { - tracing::error!("Unexpected error during message handling"); - } - } - - // Continue monitoring despite errors - tracing::debug!( - "Continuing network monitoring despite message handling error" - ); - } + tokio::select! { + received = receiver.recv() => { + match received { + None => {tracing::warn!("DashSpvClientCommand channel closed.");}, + Some(command) => { + self.handle_command(command).await.unwrap_or_else(|e| tracing::error!("Failed to handle command: {}", e)); } } - Ok(None) => { - // No message available, brief pause before continuing - tokio::time::sleep(Duration::from_millis(100)).await; - } - Err(e) => { - // Handle specific network error types - if let crate::error::NetworkError::ConnectionFailed(msg) = &e { - if msg.contains("No connected peers") || self.network.peer_count() == 0 - { - tracing::warn!("All peers disconnected during monitoring, checking connection health"); - - // Wait for potential reconnection - let mut wait_count = 0; - while wait_count < 10 && self.network.peer_count() == 0 { - tokio::time::sleep(Duration::from_millis(500)).await; - wait_count += 1; + } + received = self.network.receive_message() => { + match received { + Ok(None) => { + continue; + } + Ok(Some(message)) => { + // Wrap message handling in comprehensive error handling + match self.handle_network_message(message).await { + Ok(_) => { + // Message handled successfully } + Err(e) => { + tracing::error!("Error handling network message: {}", e); + + // Categorize error severity + match &e { + SpvError::Network(_) => { + tracing::warn!("Network error during message handling - may recover automatically"); + } + SpvError::Storage(_) => { + tracing::error!("Storage error during message handling - this may affect data consistency"); + } + SpvError::Validation(_) => { + tracing::warn!("Validation error during message handling - message rejected"); + } + _ => { + tracing::error!("Unexpected error during message handling"); + } + } - if self.network.peer_count() > 0 { - tracing::info!( - "✅ Reconnected to {} peer(s), resuming monitoring", - self.network.peer_count() - ); - continue; - } else { - tracing::warn!( - "No peers available after waiting, will retry monitoring" + // Continue monitoring despite errors + tracing::debug!( + "Continuing network monitoring despite message handling error" ); } } - } + }, + Err(err) => { + // Handle specific network error types + if let crate::error::NetworkError::ConnectionFailed(msg) = &err { + if msg.contains("No connected peers") || self.network.peer_count() == 0 { + tracing::warn!("All peers disconnected during monitoring, checking connection health"); + + // Wait for potential reconnection + let mut wait_count = 0; + while wait_count < 10 && self.network.peer_count() == 0 { + tokio::time::sleep(Duration::from_millis(500)).await; + wait_count += 1; + } - tracing::error!("Network error during monitoring: {}", e); - tokio::time::sleep(Duration::from_secs(5)).await; + if self.network.peer_count() > 0 { + tracing::info!( + "✅ Reconnected to {} peer(s), resuming monitoring", + self.network.peer_count() + ); + continue + } else { + tracing::warn!( + "No peers available after waiting, will retry monitoring" + ); + } + } + } + + tracing::error!("Network error during monitoring: {}", err); + tokio::time::sleep(Duration::from_secs(5)).await; + } } - }, - Err(_) => { - // Timeout occurred - this is expected and allows checking running state - // Continue the loop to check if we should stop + } + _ = tokio::time::sleep(MESSAGE_RECEIVE_TIMEOUT) => {} + _ = token.cancelled() => { + log::debug!("DashSpvClient run loop cancelled"); + break } } } @@ -558,6 +570,54 @@ impl< Ok(()) } + pub async fn run_until_shutdown( + mut self, + receiver: UnboundedReceiver, + shutdown_token: CancellationToken, + ) -> Result<()> { + let client_token = shutdown_token.clone(); + + let client_task = tokio::spawn(async move { + let result = self.run(receiver, client_token).await; + if let Err(e) = &result { + tracing::error!("Error running client: {}", e); + } + if let Err(e) = self.stop().await { + tracing::error!("Error stopping client: {}", e); + } + result + }); + + let shutdown_task = tokio::spawn(async move { + let _ = tokio::signal::ctrl_c().await; + tracing::debug!("Shutdown signal received"); + shutdown_token.cancel(); + }); + + let (client_result, _) = tokio::join!(client_task, shutdown_task); + client_result.map_err(|e| SpvError::General(format!("client_task panicked: {e}")))? + } + + async fn handle_command(&mut self, command: DashSpvClientCommand) -> Result<()> { + match command { + DashSpvClientCommand::GetQuorumByHeight { + height, + quorum_type, + quorum_hash, + sender, + } => { + let result = self.get_quorum_at_height(height, quorum_type, quorum_hash); + if sender.send(result).is_err() { + return Err(SpvError::ChannelFailure( + format!("GetQuorumByHeight({height}, {quorum_type}, {quorum_hash})"), + "Failed to send quorum result".to_string(), + )); + } + } + } + Ok(()) + } + /// Handle incoming network messages during monitoring. pub(super) async fn handle_network_message( &mut self, diff --git a/dash-spv/src/error.rs b/dash-spv/src/error.rs index cd8ca0879..85595cc63 100644 --- a/dash-spv/src/error.rs +++ b/dash-spv/src/error.rs @@ -6,6 +6,9 @@ use thiserror::Error; /// Main error type for the Dash SPV client. #[derive(Debug, Error)] pub enum SpvError { + #[error("Channel failure for: {0} - Failure: {1}")] + ChannelFailure(String, String), + #[error("Network error: {0}")] Network(#[from] NetworkError), @@ -32,6 +35,9 @@ pub enum SpvError { #[error("Wallet error: {0}")] Wallet(#[from] WalletError), + + #[error("Quorum lookup error: {0}")] + QuorumLookupError(String), } /// Parse-related errors. diff --git a/dash-spv/src/main.rs b/dash-spv/src/main.rs index 5436653d7..ddf4eebf1 100644 --- a/dash-spv/src/main.rs +++ b/dash-spv/src/main.rs @@ -6,12 +6,11 @@ use std::process; use std::sync::Arc; use clap::{Arg, Command}; -use tokio::signal; - use dash_spv::terminal::TerminalGuard; use dash_spv::{ClientConfig, DashSpvClient, Network}; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_manager::wallet_manager::WalletManager; +use tokio_util::sync::CancellationToken; #[tokio::main] async fn main() { @@ -631,32 +630,10 @@ async fn run_client { - if let Err(e) = result { - tracing::error!("Network monitoring failed: {}", e); - } - } - _ = signal::ctrl_c() => { - tracing::info!("Received shutdown signal (Ctrl-C)"); - - // Stop the client immediately - tracing::info!("Stopping SPV client..."); - if let Err(e) = client.stop().await { - tracing::error!("Error stopping client: {}", e); - } else { - tracing::info!("SPV client stopped successfully"); - } - return Ok(()); - } - } + let (_command_sender, command_receiver) = tokio::sync::mpsc::unbounded_channel(); + let shutdown_token = CancellationToken::new(); - // Stop the client (if monitor_network exited normally) - tracing::info!("Stopping SPV client..."); - if let Err(e) = client.stop().await { - tracing::error!("Error stopping client: {}", e); - } + client.run_until_shutdown(command_receiver, shutdown_token).await?; - tracing::info!("SPV client stopped"); Ok(()) } From a28f0ccb5acc9e377670fcbab091f5d51ae25048 Mon Sep 17 00:00:00 2001 From: pauldelucia Date: Thu, 27 Nov 2025 14:06:15 +0700 Subject: [PATCH 2/2] feat(spv): check quorum verification status before returning on lookup --- dash-spv/src/client/queries.rs | 72 ++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 24 deletions(-) diff --git a/dash-spv/src/client/queries.rs b/dash-spv/src/client/queries.rs index bb0be8c3b..645dd1944 100644 --- a/dash-spv/src/client/queries.rs +++ b/dash-spv/src/client/queries.rs @@ -10,6 +10,7 @@ use crate::error::{Result, SpvError}; use crate::network::NetworkManager; use crate::storage::StorageManager; use crate::types::AddressBalance; +use dashcore::sml::llmq_entry_verification::LLMQEntryVerificationStatus; use dashcore::sml::llmq_type::LLMQType; use dashcore::sml::masternode_list::MasternodeList; use dashcore::sml::masternode_list_engine::MasternodeListEngine; @@ -84,47 +85,70 @@ impl< // We have the masternode list, now look for the quorum match ml.quorums.get(&quorum_type) { Some(quorums) => match quorums.get(&quorum_hash) { - Some(quorum) => { - tracing::debug!( - "Found quorum type {} at height {} with hash {}", + // Found the quorum, now check its verification status + Some(q) => match &q.verified { + // TODO only return verified once validation is reliable + LLMQEntryVerificationStatus::Verified => { + tracing::debug!( + "Found verified quorum type {} at height {} with hash {}", + quorum_type, + height, + hex::encode(quorum_hash) + ); + Ok(q.clone()) + } + LLMQEntryVerificationStatus::Unknown + | LLMQEntryVerificationStatus::Skipped(_) => { + tracing::warn!( + "Quorum type {} at height {} with hash {} found but not yet verified (status: {:?})", quorum_type, height, - hex::encode(quorum_hash) + hex::encode(quorum_hash), + q.verified ); - Ok(quorum.clone()) - } + Ok(q.clone()) + } + LLMQEntryVerificationStatus::Invalid(err) => { + let message = format!( + "Quorum found but invalid: type {} at height {} with hash {} (reason: {:?})", + quorum_type, + height, + hex::encode(quorum_hash), + err + ); + tracing::warn!(message); + Err(SpvError::QuorumLookupError(message)) + } + }, None => { let message = format!("Quorum not found: type {} at height {} with hash {} (masternode list exists with {} quorums of this type)", - quorum_type, - height, - hex::encode(quorum_hash), - quorums.len()); + quorum_type, + height, + hex::encode(quorum_hash), + quorums.len()); tracing::warn!(message); Err(SpvError::QuorumLookupError(message)) } }, None => { - tracing::warn!( + let message = format!( "No quorums of type {} found at height {} (masternode list exists)", - quorum_type, - height - ); - Err(SpvError::QuorumLookupError(format!( - "No quorums of type {} found at height {}", quorum_type, height - ))) + ); + tracing::warn!(message); + Err(SpvError::QuorumLookupError(message)) } } } None => { - tracing::warn!( - "No masternode list found at height {} - cannot retrieve quorum", - height + let message = format!( + "Masternode list not found at height {} when looking for quorum type {} with hash {}", + height, + quorum_type, + hex::encode(quorum_hash) ); - Err(SpvError::QuorumLookupError(format!( - "No masternode list found at height {}", - height - ))) + tracing::warn!(message); + Err(SpvError::QuorumLookupError(message)) } } }