Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions dash-spv/src/client/chainlock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,17 @@ impl<
pub fn update_chainlock_validation(&self) -> Result<bool> {
// Check if masternode sync has an engine available
if let Some(engine) = self.sync_manager.get_masternode_engine() {
// Clone the engine for the ChainLockManager
// Clone the engine for the ChainLockManager and QuorumLookup
let engine_arc = Arc::new(engine.clone());
self.chainlock_manager.set_masternode_engine(engine_arc);

// Update ChainLockManager for ChainLock validation
self.chainlock_manager.set_masternode_engine(engine_arc.clone());
tracing::info!("Updated ChainLockManager with masternode engine for full validation");

// Update QuorumLookup for quorum queries
self.quorum_lookup.set_engine(engine_arc);
tracing::info!("Updated QuorumLookup with masternode engine for quorum queries");

// Note: Pending ChainLocks will be validated when they are next processed
// or can be triggered by calling validate_pending_chainlocks separately
// when mutable access to storage is available
Expand Down
40 changes: 39 additions & 1 deletion dash-spv/src/client/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use crate::types::{ChainState, DetailedSyncProgress, MempoolState, SpvEvent, Spv
use crate::validation::ValidationManager;
use key_wallet_manager::wallet_interface::WalletInterface;

use super::{BlockProcessingTask, ClientConfig, StatusDisplay};
use super::{BlockProcessingTask, ClientConfig, QuorumLookup, StatusDisplay};

/// Main Dash SPV client with generic trait-based architecture.
///
Expand Down Expand Up @@ -135,6 +135,12 @@ pub struct DashSpvClient<W: WalletInterface, N: NetworkManager, S: StorageManage
pub(super) sync_manager: SequentialSyncManager<S, N, W>,
pub(super) validation: ValidationManager,
pub(super) chainlock_manager: Arc<ChainLockManager>,
/// Thread-safe quorum and masternode lookup interface.
///
/// This component provides shared access to masternode lists and quorum data
/// without requiring exclusive access to the sync_manager. Applications can
/// clone the Arc and perform queries from multiple threads.
pub(super) quorum_lookup: Arc<QuorumLookup>,
pub(super) running: Arc<RwLock<bool>>,
#[cfg(feature = "terminal-ui")]
pub(super) terminal_ui: Option<Arc<TerminalUI>>,
Expand Down Expand Up @@ -177,6 +183,38 @@ impl<
&self.chainlock_manager
}

/// Get reference to quorum lookup component.
///
/// This component provides thread-safe access to masternode lists and quorum data.
/// The returned `Arc<QuorumLookup>` can be cheaply cloned and shared across threads.
///
/// # Example
///
/// ```rust,no_run
/// # use dash_spv::client::DashSpvClient;
/// # async fn example(client: &DashSpvClient<
/// # key_wallet_manager::wallet_manager::WalletManager<key_wallet::wallet::managed_wallet_info::ManagedWalletInfo>,
/// # dash_spv::network::manager::PeerNetworkManager,
/// # dash_spv::storage::DiskStorageManager
/// # >) {
/// // Get the quorum lookup component
/// let quorum_lookup = client.quorum_lookup();
///
/// // Clone for use in another thread/task
/// let lookup_clone = quorum_lookup.clone();
///
/// // Use in async task
/// tokio::spawn(async move {
/// if let Some(quorum) = lookup_clone.get_quorum_at_height(100000, 1, &[0u8; 32]).await {
/// println!("Found quorum!");
/// }
/// });
/// # }
/// ```
pub fn quorum_lookup(&self) -> &Arc<QuorumLookup> {
&self.quorum_lookup
}

/// Get mutable reference to sync manager (for testing).
#[cfg(test)]
pub fn sync_manager_mut(&mut self) -> &mut SequentialSyncManager<S, N, W> {
Expand Down
6 changes: 5 additions & 1 deletion dash-spv/src/client/lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::validation::ValidationManager;
use dashcore::network::constants::NetworkExt;
use key_wallet_manager::wallet_interface::WalletInterface;

use super::{BlockProcessor, ClientConfig, DashSpvClient};
use super::{BlockProcessor, ClientConfig, DashSpvClient, QuorumLookup};

impl<
W: WalletInterface + Send + Sync + 'static,
Expand Down Expand Up @@ -66,6 +66,9 @@ impl<
// Create ChainLock manager
let chainlock_manager = Arc::new(ChainLockManager::new(true));

// Create quorum lookup component
let quorum_lookup = Arc::new(QuorumLookup::new());

// Create block processing channel
let (block_processor_tx, _block_processor_rx) = mpsc::unbounded_channel();

Expand All @@ -88,6 +91,7 @@ impl<
sync_manager,
validation,
chainlock_manager,
quorum_lookup,
running: Arc::new(RwLock::new(false)),
#[cfg(feature = "terminal-ui")]
terminal_ui: None,
Expand Down
2 changes: 2 additions & 0 deletions dash-spv/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ mod lifecycle;
mod mempool;
mod progress;
mod queries;
mod quorum_lookup;
mod sync_coordinator;
mod transactions;

Expand All @@ -56,6 +57,7 @@ pub use block_processor::{BlockProcessingTask, BlockProcessor};
pub use config::ClientConfig;
pub use filter_sync::FilterSyncCoordinator;
pub use message_handler::MessageHandler;
pub use quorum_lookup::QuorumLookup;
pub use status_display::StatusDisplay;

// Re-export the main client struct
Expand Down
93 changes: 36 additions & 57 deletions dash-spv/src/client/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,68 +70,47 @@ impl<

/// Get a quorum entry by type and hash at a specific block height.
/// Returns None if the quorum is not found.
///
/// # Note
///
/// This is a synchronous convenience wrapper that blocks on the async QuorumLookup.
/// For better performance and to avoid blocking, prefer using the async version directly:
///
/// Returns `None` if:
/// - No Tokio runtime is available (fails gracefully instead of panicking)
/// - The quorum is not found
/// - Masternode sync is not yet complete
///
/// ```rust,no_run
/// # use dash_spv::client::DashSpvClient;
/// # async fn example(client: &DashSpvClient<
/// # key_wallet_manager::wallet_manager::WalletManager<key_wallet::wallet::managed_wallet_info::ManagedWalletInfo>,
/// # dash_spv::network::manager::PeerNetworkManager,
/// # dash_spv::storage::DiskStorageManager
/// # >) {
/// let quorum_lookup = client.quorum_lookup();
/// let quorum_hash = [0u8; 32]; // Placeholder - use actual hash
/// if let Some(quorum) = quorum_lookup.get_quorum_at_height(100000, 1, &quorum_hash).await {
/// println!("Found quorum!");
/// }
/// # }
/// ```
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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);

// 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) {
Some(quorum) => {
tracing::debug!(
"Found quorum type {} at height {} with hash {}",
quorum_type,
height,
hex::encode(quorum_hash)
);
Some(quorum)
}
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
}
},
None => {
tracing::warn!(
"No quorums of type {} found at height {} (masternode list exists)",
quorum_type,
height
);
None
}
}
}
None => {
tracing::warn!(
"No masternode list found at height {} - cannot retrieve quorum",
height
);
None
}
}
) -> Option<QualifiedQuorumEntry> {
// Delegate to the QuorumLookup component
// This requires blocking on the async call
// Return None gracefully if no Tokio runtime is available
let handle = tokio::runtime::Handle::try_current().ok()?;

tokio::task::block_in_place(move || {
handle.block_on(async {
self.quorum_lookup.get_quorum_at_height(height, quorum_type, quorum_hash).await
})
})
}

// ============ Balance Queries ============
Expand Down
Loading
Loading