use std::sync::Arc;
use std::time::Duration;
use electrum_client_netagnostic::{Client, Config, ConfigBuilder, ElectrumApi};
use serde_json::Value;
use super::peer::ServerPeer;
use anyhow::{Context, Result};
const CANDIDATE_TIMEOUT: Duration = Duration::from_secs(10);
pub struct ElectrumClient {
client: Option<Arc<Client>>,
}
#[derive(PartialEq, Eq)]
pub enum Protocol {
TCP,
SSL,
ANY,
}
async fn async_call(
client: Arc<electrum_client_netagnostic::Client>,
method_name: impl Into<String> + Send + 'static,
params: impl IntoIterator<Item = electrum_client_netagnostic::Param> + Send + 'static,
) -> Result<Value> {
let method_name = method_name.into();
let params_vec: Vec<electrum_client_netagnostic::Param> = params.into_iter().collect();
Ok(tokio::task::spawn_blocking(move || {
client.raw_call(&method_name, params_vec)
})
.await??)
}
async fn connect(url: String, config: Config) -> Result<Client> {
Ok(tokio::task::spawn_blocking(move || Client::from_config(&url, config)).await??)
}
impl ElectrumClient {
async fn connect_ssl(peer: &dyn ServerPeer, config: Config) -> Result<Self> {
let port = peer.ssl_port().context("SSL not supported by peer")?;
let client = Arc::new(connect(format!("ssl://{}:{}", peer.ip(), port), config).await?);
async_call(client.clone(), "server.ping", None)
.await
.context("Server did not respond to ping")?;
Ok(Self {
client: Some(client),
})
}
async fn connect_tcp(peer: &dyn ServerPeer, config: Config) -> Result<Self> {
let port = peer.tcp_port().context("TCP not supported by peer")?;
let client = Arc::new(connect(format!("tcp://{}:{}", peer.ip(), port), config).await?);
async_call(client.clone(), "server.ping", None)
.await
.context("Server did not respond to ping")?;
Ok(Self {
client: Some(client),
})
}
fn default_config() -> Config {
ConfigBuilder::new()
.timeout(Some(CANDIDATE_TIMEOUT))
.validate_domain(false)
.build()
}
pub async fn connect(peer: &dyn ServerPeer, protocol: Protocol) -> Result<Self> {
trace!(
"discover: Connecting to electrum server {}",
peer.pretty_hostname()
);
if protocol == Protocol::SSL {
return Self::connect_ssl(peer, Self::default_config()).await;
}
if protocol == Protocol::TCP {
return Self::connect_tcp(peer, Self::default_config()).await;
}
debug_assert!(protocol == Protocol::ANY);
if let Ok(c) = Self::connect_ssl(peer, Self::default_config()).await {
return Ok(c);
}
if let Ok(c) = Self::connect_tcp(peer, Self::default_config()).await {
return Ok(c);
}
bail!("Unable to connec to peer via SSL or TCP");
}
pub async fn connect_all(peer: &dyn ServerPeer) -> (Result<Self>, Result<Self>) {
let ssl = Self::connect_ssl(peer, Self::default_config()).await;
let tcp = Self::connect_tcp(peer, Self::default_config()).await;
(ssl, tcp)
}
pub async fn call(
&self,
method_name: impl Into<String> + Send + 'static,
params: impl IntoIterator<Item = electrum_client_netagnostic::Param> + Send + 'static,
) -> Result<Value> {
return async_call(
self.client.as_ref().context("Not connected")?.clone(),
method_name,
params,
)
.await;
}
}