Skip to content
Open
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
80 changes: 79 additions & 1 deletion packages/rs-sdk-trusted-context-provider/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use dpp::version::PlatformVersion;

use lru::LruCache;
use reqwest::Client;
use serde::Deserialize;
use std::collections::HashMap;
use std::error::Error as StdError;
#[cfg(not(target_arch = "wasm32"))]
Expand All @@ -33,7 +34,6 @@ use std::sync::{Arc, Mutex};
#[cfg(not(target_arch = "wasm32"))]
use std::time::Duration;
use tracing::{debug, info};
#[cfg(not(target_arch = "wasm32"))]
use url::Url;

/// A trusted HTTP-based context provider that fetches quorum information
Expand Down Expand Up @@ -65,6 +65,20 @@ pub struct TrustedHttpContextProvider {
refetch_if_not_found: bool,
}

#[derive(Debug, Deserialize)]
struct MasternodeEntry {
address: String,
status: String,
#[serde(rename = "versionCheck")]
version_check: Option<String>,
}

#[derive(Debug, Deserialize)]
struct MasternodeDiscoveryResponse {
success: bool,
data: Vec<MasternodeEntry>,
}

impl TrustedHttpContextProvider {
/// Verify that a URL's domain resolves
#[cfg(not(target_arch = "wasm32"))]
Expand Down Expand Up @@ -229,6 +243,70 @@ impl TrustedHttpContextProvider {
current_count + previous_count
}

/// Fetch DAPI HTTPS addresses from the masternode discovery endpoint
pub async fn fetch_masternode_addresses(
&self,
) -> Result<Vec<Url>, TrustedContextProviderError> {
let url = format!("{}/masternodes", self.base_url);
debug!(
"Fetching masternode addresses from trusted resource: {}",
url
);

let response = self.client.get(&url).send().await?;
if !response.status().is_success() {
return Err(TrustedContextProviderError::NetworkError(format!(
"HTTP {} from {}",
response.status(),
url
)));
}

let body = response.text().await?;
let parsed: MasternodeDiscoveryResponse = serde_json::from_str(&body)?;

if !parsed.success {
return Err(TrustedContextProviderError::NetworkError(
"Masternode discovery response indicated failure".to_string(),
));
}

let dapi_port = match self.network {
Network::Dash => 443,
Network::Testnet => 1443,
_ => 443,
};

let mut addresses = Vec::new();
for entry in parsed
.data
.into_iter()
.filter(|m| m.status == "ENABLED" && m.version_check.as_deref() == Some("success"))
{
let host_port = entry.address;
let host = host_port
.rsplit_once(':')
.map(|(h, _)| h)
.unwrap_or(host_port.as_str());
let https_url = format!("https://{}:{}", host, dapi_port);
let url = url::Url::parse(&https_url).map_err(|e| {
TrustedContextProviderError::NetworkError(format!(
"Invalid masternode URL '{}': {}",
https_url, e
))
})?;
addresses.push(url);
}

if addresses.is_empty() {
return Err(TrustedContextProviderError::NetworkError(
"No eligible masternode addresses discovered".to_string(),
));
}

Ok(addresses)
}

/// Fetch current quorums from the HTTP endpoint
pub async fn fetch_current_quorums(
&self,
Expand Down
24 changes: 24 additions & 0 deletions packages/wasm-sdk/src/context_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,30 @@ impl WasmTrustedContext {
})
}

pub async fn fetch_masternode_addresses(
&self,
) -> Result<rs_dapi_client::AddressList, ContextProviderError> {
let urls = self.inner.fetch_masternode_addresses().await.map_err(|e| {
ContextProviderError::Generic(format!("Failed to fetch masternodes: {}", e))
})?;

let mut addresses = Vec::new();
for url in urls {
let uri = dash_sdk::sdk::Uri::from_maybe_shared(url.to_string()).map_err(|e| {
ContextProviderError::Generic(format!("Invalid masternode URI '{}': {}", url, e))
})?;
let address = rs_dapi_client::Address::try_from(uri).map_err(|e| {
ContextProviderError::Generic(format!(
"Invalid masternode address '{}': {}",
url, e
))
})?;
addresses.push(address);
}

Ok(rs_dapi_client::AddressList::from_iter(addresses))
}

/// Add a data contract to the known contracts cache
pub fn add_known_contract(&self, contract: DataContract) {
self.inner.add_known_contract(contract);
Expand Down
Loading
Loading