Added DHT peer resolver, fixed MTU
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
//! Discovery is a cancellable bootstrap job, never the network's event loop.
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use iroh::{EndpointAddr, EndpointId};
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
use std::time::Duration;
|
||||
use tsunagi::config::DiscoveryPolicy;
|
||||
use tsunagi::discovery::{BoxFuture, Candidate, NetworkDiscovery, SharedMemoryDiscovery};
|
||||
use tsunagi::identity::DiscoveryKey;
|
||||
use tsunagi::testing::{local_config, network, wait_for_peers, wait_until};
|
||||
use tsunagi::{Agent, Result};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct Counted {
|
||||
table: SharedMemoryDiscovery,
|
||||
reads: AtomicUsize,
|
||||
writes: AtomicUsize,
|
||||
}
|
||||
|
||||
impl NetworkDiscovery for Counted {
|
||||
fn name(&self) -> &str {
|
||||
"counted"
|
||||
}
|
||||
fn publish<'a>(&'a self, key: DiscoveryKey, addr: EndpointAddr) -> BoxFuture<'a, Result<()>> {
|
||||
self.writes.fetch_add(1, Ordering::SeqCst);
|
||||
self.table.publish(key, addr)
|
||||
}
|
||||
fn unpublish<'a>(&'a self, key: DiscoveryKey, id: EndpointId) -> BoxFuture<'a, Result<()>> {
|
||||
self.table.unpublish(key, id)
|
||||
}
|
||||
fn resolve<'a>(&'a self, key: DiscoveryKey) -> BoxFuture<'a, Result<Vec<Candidate>>> {
|
||||
self.reads.fetch_add(1, Ordering::SeqCst);
|
||||
self.table.resolve(key)
|
||||
}
|
||||
}
|
||||
|
||||
fn policy() -> DiscoveryPolicy {
|
||||
DiscoveryPolicy {
|
||||
publish_interval: Duration::from_millis(100),
|
||||
lookup_interval: Duration::from_millis(50),
|
||||
max_lookup_interval: Duration::from_millis(100),
|
||||
reconnect_delay: Duration::from_millis(300),
|
||||
request_timeout: Duration::from_secs(2),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn connected_peers_only_publish_and_isolated_peers_resume_search() {
|
||||
let discovery = Arc::new(Counted::default());
|
||||
let first_dir = tempfile::tempdir().unwrap();
|
||||
let second_dir = tempfile::tempdir().unwrap();
|
||||
let config = |path: &std::path::Path| {
|
||||
local_config(path)
|
||||
.with_discovery(discovery.clone())
|
||||
.with_discovery_policy(policy())
|
||||
};
|
||||
let first = Agent::spawn(config(first_dir.path())).await.unwrap();
|
||||
let second = Agent::spawn(config(second_dir.path())).await.unwrap();
|
||||
let (name, secret) = network("discovery-lifecycle");
|
||||
let id = first.join_network(&name, &secret).await.unwrap();
|
||||
second.join_network(&name, &secret).await.unwrap();
|
||||
wait_for_peers(&first, id, 1).await;
|
||||
wait_for_peers(&second, id, 1).await;
|
||||
let reads = discovery.reads.load(Ordering::SeqCst);
|
||||
let writes = discovery.writes.load(Ordering::SeqCst);
|
||||
wait_until("publications continue while connected", || async {
|
||||
(discovery.writes.load(Ordering::SeqCst) >= writes + 6).then_some(())
|
||||
})
|
||||
.await;
|
||||
assert_eq!(discovery.reads.load(Ordering::SeqCst), reads);
|
||||
second.shutdown().await;
|
||||
wait_for_peers(&first, id, 0).await;
|
||||
wait_until("isolation resumes discovery", || async {
|
||||
(discovery.reads.load(Ordering::SeqCst) > reads).then_some(())
|
||||
})
|
||||
.await;
|
||||
first.shutdown().await;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Stalled;
|
||||
impl NetworkDiscovery for Stalled {
|
||||
fn name(&self) -> &str {
|
||||
"stalled"
|
||||
}
|
||||
fn publish<'a>(&'a self, _: DiscoveryKey, _: EndpointAddr) -> BoxFuture<'a, Result<()>> {
|
||||
Box::pin(std::future::pending())
|
||||
}
|
||||
fn unpublish<'a>(&'a self, _: DiscoveryKey, _: EndpointId) -> BoxFuture<'a, Result<()>> {
|
||||
Box::pin(std::future::pending())
|
||||
}
|
||||
fn resolve<'a>(&'a self, _: DiscoveryKey) -> BoxFuture<'a, Result<Vec<Candidate>>> {
|
||||
Box::pin(std::future::pending())
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn stalled_discovery_does_not_block_status_or_shutdown() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let agent = Agent::spawn(local_config(dir.path()).with_discovery(Arc::new(Stalled)))
|
||||
.await
|
||||
.unwrap();
|
||||
let (name, secret) = network("stalled-discovery");
|
||||
let id = agent.join_network(&name, &secret).await.unwrap();
|
||||
// A recheck schedules work; it must not await the unreachable backend.
|
||||
agent.recheck_network(id).await.unwrap();
|
||||
tokio::time::timeout(Duration::from_secs(2), agent.network_status(id))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
tokio::time::timeout(Duration::from_secs(2), agent.shutdown())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
//! Local Mainline Testnet, real iroh authentication, and durable agent state.
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use std::time::Duration;
|
||||
use tsunagi::testing::{local_config, network, wait_for_peers};
|
||||
use tsunagi::{Agent, config::DiscoveryPolicy, discovery::MainlineDiscovery};
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "uses public Mainline DHT and iroh relay services"]
|
||||
async fn public_dht_finds_and_authenticates_two_agents() {
|
||||
tsunagi::testing::init_tracing();
|
||||
let a_dir = tempfile::tempdir().unwrap();
|
||||
let b_dir = tempfile::tempdir().unwrap();
|
||||
let config = |path: &std::path::Path| {
|
||||
tsunagi::config::AgentConfig::new(tsunagi::config::StoragePaths::under(path))
|
||||
.with_transport(tsunagi::config::TransportPolicy::N0Defaults)
|
||||
.with_dht(MainlineDiscovery::default())
|
||||
};
|
||||
let a = Agent::spawn(config(a_dir.path())).await.unwrap();
|
||||
let b = Agent::spawn(config(b_dir.path())).await.unwrap();
|
||||
let (name, secret) = network("mainline-public-smoke");
|
||||
let id = a.join_network(&name, &secret).await.unwrap();
|
||||
b.join_network(&name, &secret).await.unwrap();
|
||||
let found = tokio::time::timeout(Duration::from_secs(180), async {
|
||||
loop {
|
||||
if !a.network_status(id).await.unwrap().peers.is_empty()
|
||||
&& !b.network_status(id).await.unwrap().peers.is_empty()
|
||||
{
|
||||
break;
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(250)).await;
|
||||
}
|
||||
})
|
||||
.await;
|
||||
a.shutdown().await;
|
||||
b.shutdown().await;
|
||||
assert!(
|
||||
found.is_ok(),
|
||||
"public DHT/relay discovery did not connect within three minutes"
|
||||
);
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn two_agents_restore_after_all_dht_records_and_addresses_are_gone() {
|
||||
let first_dir = tempfile::tempdir().unwrap();
|
||||
let second_dir = tempfile::tempdir().unwrap();
|
||||
let (name, secret) = network("dht-restart");
|
||||
let mut identities = None;
|
||||
for _ in 0..2 {
|
||||
if identities.is_some() {
|
||||
for path in [first_dir.path(), second_dir.path()] {
|
||||
let cache = tsunagi::config::StoragePaths::under(path).cache_dir;
|
||||
if cache.exists() {
|
||||
std::fs::remove_dir_all(cache).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
// A fresh Testnet means no old rendezvous record survives. The second
|
||||
// pass restores the networks from SQLite without another join command.
|
||||
let net = mainline::Testnet::builder(5).build().unwrap();
|
||||
let config = |path: &std::path::Path| {
|
||||
local_config(path)
|
||||
.with_dht(MainlineDiscovery::local_testnet(&net.bootstrap).unwrap())
|
||||
.with_discovery_policy(DiscoveryPolicy {
|
||||
lookup_interval: Duration::from_millis(100),
|
||||
max_lookup_interval: Duration::from_millis(300),
|
||||
..Default::default()
|
||||
})
|
||||
};
|
||||
let (a, b) = tokio::join!(
|
||||
Agent::spawn(config(first_dir.path())),
|
||||
Agent::spawn(config(second_dir.path()))
|
||||
);
|
||||
let a = a.unwrap();
|
||||
let b = b.unwrap();
|
||||
let id = tsunagi::identity::NetworkKeys::derive(&name, &secret).network_id();
|
||||
if let Some(old) = identities {
|
||||
assert_eq!((a.endpoint_id(), b.endpoint_id()), old);
|
||||
} else {
|
||||
identities = Some((a.endpoint_id(), b.endpoint_id()));
|
||||
let (ra, rb) = tokio::join!(
|
||||
a.join_network(&name, &secret),
|
||||
b.join_network(&name, &secret)
|
||||
);
|
||||
assert_eq!(ra.unwrap(), id);
|
||||
assert_eq!(rb.unwrap(), id);
|
||||
}
|
||||
wait_for_peers(&a, id, 1).await;
|
||||
wait_for_peers(&b, id, 1).await;
|
||||
a.shutdown().await;
|
||||
b.shutdown().await;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user