#[cfg(test)]
mod test_client;
use crate::client::Error;
use backoff::ExponentialBackoff;
use dirs_next::home_dir;
use eyre::{eyre, Context, Result};
use sn_interface::types::{Keypair, PublicKey};
use std::time::Duration;
use std::{
collections::BTreeSet, fs::File, future::Future, io::BufReader, net::SocketAddr, path::Path,
};
#[cfg(test)]
pub use test_client::{create_test_client, create_test_client_with, init_test_logger};
pub type ClientResult<T> = Result<T, Error>;
#[allow(clippy::needless_question_mark)]
pub async fn run_w_backoff_delayed<R, F, Fut>(f: F, _retries: u8, delay: usize) -> Result<R, Error>
where
F: Fn() -> Fut,
Fut: Future<Output = Result<R, backoff::Error<Error>>>,
{
tokio::time::sleep(tokio::time::Duration::from_secs(delay as u64)).await;
let res = retry(
|| async { Ok(f().await?) },
tokio::time::Duration::from_secs(5),
tokio::time::Duration::from_secs(180),
)
.await;
if res.is_err() {
Err(Error::NoResponse)
} else {
res
}
}
fn retry<R, E, Fn, Fut>(
op: Fn,
initial_interval: Duration,
max_elapsed_time: Duration,
) -> impl Future<Output = Result<R, E>>
where
Fn: FnMut() -> Fut,
Fut: Future<Output = Result<R, backoff::Error<E>>>,
{
let backoff = ExponentialBackoff {
initial_interval,
max_interval: max_elapsed_time,
max_elapsed_time: Some(max_elapsed_time),
..Default::default()
};
backoff::future::retry(backoff, op)
}
const GENESIS_CONN_INFO_FILEPATH: &str = ".safe/node/node_connection_info.config";
pub fn gen_ed_keypair() -> Keypair {
let mut rng = rand::thread_rng();
Keypair::new_ed25519(&mut rng)
}
pub fn read_network_conn_info() -> Result<(bls::PublicKey, BTreeSet<SocketAddr>)> {
let user_dir = home_dir().ok_or_else(|| eyre!("Could not fetch home directory"))?;
let conn_info_path = user_dir.join(Path::new(GENESIS_CONN_INFO_FILEPATH));
let file = File::open(&conn_info_path).with_context(|| {
format!(
"Failed to open node connection information file at '{}'",
conn_info_path.display(),
)
})?;
let reader = BufReader::new(file);
let (genesis_key_hex, bootstrap_nodes): (String, BTreeSet<SocketAddr>) =
serde_json::from_reader(reader).with_context(|| {
format!(
"Failed to parse content of node connection information file at '{}'",
conn_info_path.display(),
)
})?;
let genesis_key = PublicKey::bls_from_hex(&genesis_key_hex)?
.bls()
.ok_or_else(|| eyre!("Unexpectedly failed to obtain (BLS) genesis key."))?;
Ok((genesis_key, bootstrap_nodes))
}
#[cfg(test)]
#[macro_export]
macro_rules! retry_loop {
($async_func:expr) => {
loop {
match $async_func.await {
Ok(val) => break val,
Err(_) => tokio::time::sleep(std::time::Duration::from_secs(2)).await,
}
}
};
}
#[cfg(test)]
#[macro_export]
macro_rules! retry_err_loop {
($async_func:expr) => {
loop {
match $async_func.await {
Ok(_) => tokio::time::sleep(std::time::Duration::from_secs(2)).await,
Err(err) => break err,
}
}
};
}
#[cfg(test)]
#[macro_export]
macro_rules! retry_loop_for_pattern {
($async_func:expr, $pattern:pat $(if $cond:expr)?) => {
loop {
let result = $async_func.await;
match &result {
$pattern $(if $cond)? => break result,
Ok(_) | Err(_) => tokio::time::sleep(std::time::Duration::from_secs(2)).await,
}
}
};
}