pub mod client_builder;
mod cmds;
mod data;
mod file_apis;
mod queries;
mod register_apis;
mod spentbook_apis;
pub use client_builder::ClientBuilder;
pub use file_apis::QueriedDataReplicas;
pub use register_apis::RegisterWriteAheadLog;
use crate::{
errors::{Error, Result},
sessions::Session,
};
use sn_dbc::Owner;
use sn_interface::{
messaging::data::{DataQueryVariant, RegisterQuery},
network_knowledge::SectionTree,
types::{Chunk, Keypair, PublicKey, RegisterAddress},
};
use std::sync::Arc;
use tokio::{sync::RwLock, time::Duration};
use tracing::debug;
use uluru::LRUCache;
pub const DEFAULT_NETWORK_CONTACTS_FILE_NAME: &str = "default";
const CHUNK_CACHE_SIZE: usize = 50;
const NETWORK_PROBE_MAX_ATTEMPTS: usize = 3;
type ChunksCache = LRUCache<Chunk, CHUNK_CACHE_SIZE>;
#[derive(Clone, Debug)]
pub struct Client {
keypair: Keypair,
dbc_owner: Owner,
session: Session,
pub(crate) query_timeout: Duration,
pub(crate) max_backoff_interval: Duration,
pub(crate) cmd_timeout: Duration,
chunks_cache: Arc<RwLock<ChunksCache>>,
}
impl Client {
#[instrument(skip_all, level = "debug")]
pub async fn connect(&self) -> Result<()> {
let query = DataQueryVariant::Register(RegisterQuery::Get(RegisterAddress {
name: xor_name::rand::random(),
tag: 1,
}));
debug!(
"Making initial contact with network. Our public addr: {:?}. Probe msg: {query:?}",
self.session.endpoint.public_addr()
);
let mut attempts = 1;
loop {
match self.send_query_without_retry(query.clone()).await {
Ok(result) if result.response.is_data_not_found() => {
let network_knowledge = self.session.network.read().await;
let sections_count = network_knowledge.known_sections_count();
let known_sap = network_knowledge.closest(&query.dst_name(), None);
debug!(
"Client has some network knowledge. Current sections \
known: {sections_count}. SAP for our startup-query: {known_sap:?}"
);
break Ok(());
}
result => {
if attempts == NETWORK_PROBE_MAX_ATTEMPTS {
break Err(Error::NetworkContacts(format!(
"failed to make initial contact with network to bootstrap to \
after {attempts} attempts, result in last attempt: {result:?}"
)));
}
attempts += 1;
warn!(
"Initial probe msg to network failed. Trying again (attempt #{}): {:?}",
attempts, result
);
}
}
}
}
pub fn keypair(&self) -> &Keypair {
&self.keypair
}
pub fn public_key(&self) -> PublicKey {
self.keypair().public_key()
}
pub fn dbc_owner(&self) -> &Owner {
&self.dbc_owner
}
pub async fn is_known_section_key(&self, section_key: &sn_dbc::PublicKey) -> bool {
self.session
.network
.read()
.await
.get_sections_dag()
.has_key(section_key)
}
pub async fn section_tree(&self) -> SectionTree {
self.session.network.read().await.clone()
}
pub fn builder() -> ClientBuilder {
ClientBuilder::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::utils::test_utils::{
create_test_client, create_test_client_with, get_dbc_owner_from_secret_key_hex,
};
use sn_interface::init_logger;
use eyre::Result;
use std::{
collections::HashSet,
net::{IpAddr, Ipv4Addr, SocketAddr},
};
#[tokio::test(flavor = "multi_thread")]
async fn client_creation() -> Result<()> {
init_logger();
let _client = create_test_client().await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
#[ignore]
async fn client_nonsense_bootstrap_fails() -> Result<()> {
init_logger();
let mut nonsense_bootstrap = HashSet::new();
let _ = nonsense_bootstrap.insert(SocketAddr::new(
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
3033,
));
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn client_creation_with_existing_keypair() -> Result<()> {
init_logger();
let full_id = Keypair::new_ed25519();
let pk = full_id.public_key();
let client = create_test_client_with(Some(full_id), None, None).await?;
assert_eq!(pk, client.public_key());
Ok(())
}
#[test]
fn client_is_send() {
init_logger();
fn require_send<T: Send>(_t: T) {}
require_send(create_test_client());
}
#[tokio::test(flavor = "multi_thread")]
async fn client_create_with_dbc_owner() -> Result<()> {
init_logger();
let dbc_owner = get_dbc_owner_from_secret_key_hex(
"81ebce8339cb2a6e5cbf8b748215ba928acff7f92557b3acfb09a5b25e920d20",
)?;
let client = create_test_client_with(None, Some(dbc_owner.clone()), None).await?;
assert_eq!(&dbc_owner, client.dbc_owner());
Ok(())
}
}