use std::{net::SocketAddr, time::Duration};
use dht_rpc::{Commit, DhtConfig, IdBytes, generic_hash};
use futures::StreamExt;
use hyperdht::{Keypair, adht::Dht};
use rusty_nodejs_repl::wait;
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
pub struct RustTestnet {
pub bootstrap: Dht,
pub bootstrap_addr: SocketAddr,
pub nodes: Vec<Dht>,
}
impl RustTestnet {
pub async fn new(node_count: usize) -> Result<Self> {
let mut bootstrap = Dht::with_config(
DhtConfig::default()
.empty_bootstrap_nodes()
.bind("127.0.0.1:0")?,
)
.await?;
let bootstrap_addr = bootstrap.local_addr()?;
bootstrap.drive();
let mut nodes = Vec::with_capacity(node_count);
for _ in 0..node_count {
let mut node = Dht::with_config(
DhtConfig::default()
.add_bootstrap_node(bootstrap_addr)
.bind("127.0.0.1:0")?,
)
.await?;
node.bootstrap().await?;
node.drive();
nodes.push(node);
}
tokio::time::sleep(Duration::from_millis(50)).await;
Ok(Self {
bootstrap,
bootstrap_addr,
nodes,
})
}
}
#[tokio::test]
async fn rs_swarm_announce_lookup() -> Result<()> {
let tn = RustTestnet::new(3).await?;
let announcer = &tn.nodes[0];
let looker = &tn.nodes[1];
let topic = IdBytes(generic_hash(b"test-topic"));
let keypair = Keypair::default();
announcer.announce(topic, keypair.clone(), vec![]).await?;
let mut lookup = looker.lookup(topic, Commit::No)?;
let mut found_keys = vec![];
while let Some(result) = lookup.next().await {
if let Ok(Some(resp)) = result {
found_keys.extend(resp.peers.iter().map(|p| p.public_key.clone()));
}
}
assert!(
found_keys.contains(&keypair.public),
"Expected to find announced public key in lookup results"
);
Ok(())
}
#[tokio::test]
async fn rs_swarm_find_peer() -> Result<()> {
let tn = RustTestnet::new(3).await?;
let server = &tn.nodes[0];
let client = &tn.nodes[1];
let keypair = Keypair::default();
let mut _listener = server.listen(keypair.clone());
tokio::spawn(async move {
loop {
wait!(10);
_ = _listener.next().await;
}
});
tokio::time::sleep(Duration::from_millis(100)).await;
let mut query = client.find_peer(&keypair.public, None)?;
let mut found = false;
while let Some(Ok(Some(resp))) = query.next().await {
if resp.peer.public_key == keypair.public {
found = true;
break;
}
}
assert!(found, "Expected to find server's public key via find_peer");
Ok(())
}
#[tokio::test]
async fn rs_swarm_unannounce() -> Result<()> {
let tn = RustTestnet::new(3).await?;
let announcer = &tn.nodes[0];
let looker = &tn.nodes[1];
let topic = IdBytes(generic_hash(b"unannounce-topic"));
let keypair = Keypair::default();
announcer.announce(topic, keypair.clone(), vec![]).await?;
let mut lookup = looker.lookup(topic, Commit::No)?;
let mut found_before = false;
while let Some(Ok(Some(resp))) = lookup.next().await {
if resp.peers.iter().any(|p| p.public_key == keypair.public) {
found_before = true;
break;
}
}
assert!(found_before, "Expected to find key after announce");
announcer.unannounce(topic, keypair.clone()).await?;
let mut lookup = looker.lookup(topic, Commit::No)?;
let mut found_after = false;
while let Some(Ok(Some(resp))) = lookup.next().await {
if resp.peers.iter().any(|p| p.public_key == keypair.public) {
found_after = true;
break;
}
}
assert!(!found_after, "Expected key to be gone after unannounce");
Ok(())
}