kaspa_wrpc_client/
resolver.rs1use std::sync::OnceLock;
6
7use crate::error::Error;
8use crate::imports::*;
9use crate::node::NodeDescriptor;
10pub use futures::future::join_all;
11use rand::seq::SliceRandom;
12use rand::thread_rng;
13use workflow_core::runtime;
14use workflow_http::get_json;
15
16const CURRENT_VERSION: usize = 2;
17const RESOLVER_CONFIG: &str = include_str!("../Resolvers.toml");
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct ResolverRecord {
21 pub address: String,
22 pub enable: Option<bool>,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct ResolverGroup {
27 pub template: String,
28 pub nodes: Vec<String>,
29 pub enable: Option<bool>,
30}
31
32#[derive(Clone, Debug, Serialize, Deserialize)]
33pub struct ResolverConfig {
34 #[serde(rename = "group")]
35 groups: Vec<ResolverGroup>,
36 #[serde(rename = "resolver")]
37 resolvers: Vec<ResolverRecord>,
38}
39
40fn try_parse_resolvers(toml: &str) -> Result<Vec<Arc<String>>> {
41 let config = toml::from_str::<ResolverConfig>(toml)?;
42
43 let mut resolvers = config
44 .resolvers
45 .into_iter()
46 .filter_map(|resolver| resolver.enable.unwrap_or(true).then_some(resolver.address))
47 .collect::<Vec<_>>();
48
49 let groups = config.groups.into_iter().filter(|group| group.enable.unwrap_or(true)).collect::<Vec<_>>();
50
51 for group in groups {
52 let ResolverGroup { template, nodes, .. } = group;
53 for node in nodes {
54 resolvers.push(template.replace('*', &node));
55 }
56 }
57
58 Ok(resolvers.into_iter().map(Arc::new).collect::<Vec<_>>())
59}
60
61#[derive(Debug)]
62struct Inner {
63 pub urls: Vec<Arc<String>>,
64 pub tls: bool,
65 public: bool,
66}
67
68impl Inner {
69 pub fn new(urls: Option<Vec<Arc<String>>>, tls: bool) -> Self {
70 if urls.as_ref().is_some_and(|urls| urls.is_empty()) {
71 panic!("Resolver: Empty URL list supplied to the constructor.");
72 }
73
74 let mut public = false;
75 let urls = urls.unwrap_or_else(|| {
76 public = true;
77 try_parse_resolvers(RESOLVER_CONFIG).expect("TOML: Unable to parse RPC Resolver list")
78 });
79
80 Self { urls, tls, public }
81 }
82}
83
84#[derive(Debug, Clone)]
93pub struct Resolver {
94 inner: Arc<Inner>,
95}
96
97impl Default for Resolver {
98 fn default() -> Self {
99 Self { inner: Arc::new(Inner::new(None, false)) }
100 }
101}
102
103impl Resolver {
104 pub fn new(urls: Option<Vec<Arc<String>>>, tls: bool) -> Self {
107 Self { inner: Arc::new(Inner::new(urls, tls)) }
108 }
109
110 pub fn urls(&self) -> Option<Vec<Arc<String>>> {
114 if self.inner.public {
115 None
116 } else {
117 Some(self.inner.urls.clone())
118 }
119 }
120
121 pub fn tls(&self) -> bool {
123 self.inner.tls
124 }
125
126 fn tls_as_str(&self) -> &'static str {
127 if self.inner.tls {
128 "tls"
129 } else {
130 "any"
131 }
132 }
133
134 fn make_url(&self, url: &str, encoding: Encoding, network_id: NetworkId) -> String {
135 static TLS: OnceLock<&'static str> = OnceLock::new();
136
137 let tls = *TLS.get_or_init(|| {
138 if runtime::is_web() {
139 let tls = js_sys::Reflect::get(&js_sys::global(), &"location".into())
140 .and_then(|location| js_sys::Reflect::get(&location, &"protocol".into()))
141 .ok()
142 .and_then(|protocol| protocol.as_string())
143 .map(|protocol| protocol.starts_with("https"))
144 .unwrap_or(false);
145 if tls {
146 "tls"
147 } else {
148 self.tls_as_str()
149 }
150 } else {
151 self.tls_as_str()
152 }
153 });
154
155 format!("{url}/v{CURRENT_VERSION}/kaspa/{network_id}/{tls}/wrpc/{encoding}")
156 }
157
158 async fn fetch_node_info(&self, url: &str, encoding: Encoding, network_id: NetworkId) -> Result<NodeDescriptor> {
160 let url = self.make_url(url, encoding, network_id);
161 let node =
162 get_json::<NodeDescriptor>(&url).await.map_err(|error| Error::custom(format!("Unable to connect to {url}: {error}")))?;
163 Ok(node)
164 }
165
166 async fn fetch(&self, encoding: Encoding, network_id: NetworkId) -> Result<NodeDescriptor> {
168 let mut urls = self.inner.urls.clone();
169 urls.shuffle(&mut thread_rng());
170
171 let mut errors = Vec::default();
172 for url in urls {
173 match self.fetch_node_info(&url, encoding, network_id).await {
174 Ok(node) => return Ok(node),
175 Err(error) => errors.push(error),
176 }
177 }
178 Err(Error::Custom(format!("Failed to connect: {:?}", errors)))
179 }
180
181 pub async fn get_node(&self, encoding: Encoding, network_id: NetworkId) -> Result<NodeDescriptor> {
183 self.fetch(encoding, network_id).await
184 }
185
186 pub async fn get_url(&self, encoding: Encoding, network_id: NetworkId) -> Result<String> {
188 let nodes = self.fetch(encoding, network_id).await?;
189 Ok(nodes.url.clone())
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn test_resolver_config_1() {
199 let toml = r#"
200 [[group]]
201 enable = true
202 template = "https://*.example.org"
203 nodes = ["alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta"]
204
205 [[group]]
206 enable = true
207 template = "https://*.example.com"
208 nodes = ["iota", "kappa", "lambda", "mu", "nu", "xi", "omicron", "pi"]
209
210 [[resolver]]
211 enable = true
212 address = "http://127.0.0.1:8888"
213 "#;
214
215 let urls = try_parse_resolvers(toml).expect("TOML: Unable to parse RPC Resolver list");
216 assert_eq!(urls.len(), 17);
218 }
219
220 #[test]
221 fn test_resolver_config_2() {
222 let _urls = try_parse_resolvers(RESOLVER_CONFIG).expect("TOML: Unable to parse RPC Resolver list");
223 }
225}