Skip to main content

pubky_testnet/
static_testnet.rs

1use crate::pubky::Pubky;
2
3use std::{
4    net::{IpAddr, Ipv4Addr, SocketAddr},
5    path::PathBuf,
6    str::FromStr,
7};
8
9use crate::Testnet;
10use http_relay::HttpRelay;
11use pubky_homeserver::{ConfigToml, DomainPort, HomeserverApp, MockDataDir};
12
13/// A simple testnet with
14///
15/// - A local DHT with a boostrap node on port 6881.
16/// - pkarr relay on port 15411.
17/// - http relay on port 15412.
18/// - A homeserver with address is hardcoded to `8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo`.
19/// - An admin server for the homeserver on port 6288.
20pub struct StaticTestnet {
21    /// Inner flexible testnet.
22    pub testnet: Testnet,
23    /// Optional path to the homeserver config file if set.
24    pub homeserver_config: Option<PathBuf>,
25    #[allow(dead_code)]
26    fixed_bootstrap_node: Option<pkarr::mainline::Dht>, // Keep alive
27    #[allow(dead_code)]
28    temp_dirs: Vec<tempfile::TempDir>, // Keep temp dirs alive for the pkarr relay
29}
30
31impl StaticTestnet {
32    /// Run a new static testnet with the default homeserver config.
33    pub async fn start() -> anyhow::Result<Self> {
34        Self::new(None).await
35    }
36
37    /// Run a new static testnet with a custom homeserver config.
38    pub async fn start_with_homeserver_config(config_path: PathBuf) -> anyhow::Result<Self> {
39        Self::new(Some(config_path)).await
40    }
41
42    /// Run a new simple testnet.
43    pub async fn new(config_path: Option<PathBuf>) -> anyhow::Result<Self> {
44        let testnet = Testnet::new().await?;
45        let fixed_boostrap = Self::run_fixed_boostrap_node(&testnet.dht.bootstrap)
46            .map_err(|e| anyhow::anyhow!("Failed to run bootstrap node on port 6881: {}", e))?;
47
48        let mut testnet = Self {
49            testnet,
50            fixed_bootstrap_node: fixed_boostrap,
51            temp_dirs: vec![],
52            homeserver_config: config_path,
53        };
54
55        testnet
56            .run_fixed_pkarr_relays()
57            .await
58            .map_err(|e| anyhow::anyhow!("Failed to run pkarr relay on port 15411: {}", e))?;
59        testnet
60            .run_fixed_http_relay()
61            .await
62            .map_err(|e| anyhow::anyhow!("Failed to run http relay on port 15412: {}", e))?;
63        testnet
64            .run_fixed_homeserver()
65            .await
66            .map_err(|e| anyhow::anyhow!("Failed to run homeserver on port 6288: {}", e))?;
67
68        Ok(testnet)
69    }
70
71    /// Create an additional homeserver with a random keypair
72    pub async fn create_random_homeserver(
73        &mut self,
74    ) -> anyhow::Result<&pubky_homeserver::HomeserverApp> {
75        self.testnet.create_random_homeserver().await
76    }
77
78    /// Create a new pubky client builder.
79    pub fn client_builder(&self) -> pubky::PubkyHttpClientBuilder {
80        self.testnet.client_builder()
81    }
82
83    /// Creates a [`pubky::PubkyHttpClient`] pre-configured to use this test network.
84    pub fn client(&self) -> Result<pubky::PubkyHttpClient, pubky::BuildError> {
85        self.testnet.client()
86    }
87
88    /// Creates a [`pubky::Pubky`] SDK facade pre-configured to use this test network.
89    ///
90    /// This is a convenience method that builds a client from `Self::client_builder`.
91    pub fn sdk(&self) -> Result<Pubky, pubky::BuildError> {
92        self.testnet.sdk()
93    }
94
95    /// Create a new pkarr client builder.
96    pub fn pkarr_client_builder(&self) -> pkarr::ClientBuilder {
97        self.testnet.pkarr_client_builder()
98    }
99
100    /// Get the homeserver in the testnet.
101    pub fn homeserver_app(&self) -> &pubky_homeserver::HomeserverApp {
102        self.testnet
103            .homeservers
104            .first()
105            .expect("homeservers should be non-empty")
106    }
107
108    /// Get the http relay in the testnet.
109    pub fn http_relay(&self) -> &HttpRelay {
110        self.testnet
111            .http_relays
112            .first()
113            .expect("http relays should be non-empty")
114    }
115
116    /// Get the pkarr relay in the testnet.
117    pub fn pkarr_relay(&self) -> &pkarr_relay::Relay {
118        self.testnet
119            .pkarr_relays
120            .first()
121            .expect("pkarr relays should be non-empty")
122    }
123
124    /// Get the bootstrap nodes for the testnet.
125    pub fn bootstrap_nodes(&self) -> Vec<String> {
126        let mut nodes = vec![];
127        if let Some(dht) = &self.fixed_bootstrap_node {
128            nodes.push(dht.info().local_addr().to_string());
129        }
130        nodes.extend(
131            self.testnet
132                .dht_bootstrap_nodes()
133                .iter()
134                .map(|node| node.to_string()),
135        );
136        nodes
137    }
138
139    /// Create a fixed bootstrap node on port 6881 if it is not already running.
140    /// If it's already running, return None.
141    fn run_fixed_boostrap_node(
142        other_bootstrap_nodes: &[String],
143    ) -> anyhow::Result<Option<pkarr::mainline::Dht>> {
144        if other_bootstrap_nodes
145            .iter()
146            .any(|node| node.contains("6881"))
147        {
148            return Ok(None);
149        }
150
151        let mut builder = pkarr::mainline::Dht::builder();
152        let dht = builder
153            .port(6881)
154            .bootstrap(other_bootstrap_nodes)
155            .server_mode()
156            .build()?;
157        Ok(Some(dht))
158    }
159
160    /// Creates a fixed pkarr relay on port 15411 with a temporary storage directory.
161    async fn run_fixed_pkarr_relays(&mut self) -> anyhow::Result<()> {
162        let temp_dir = tempfile::tempdir()?; // Gets cleaned up automatically when it drops
163        let mut builder = pkarr_relay::Relay::builder();
164        builder
165            .http_port(15411)
166            .storage(temp_dir.path().to_path_buf())
167            .disable_rate_limiter()
168            .pkarr(|pkarr| {
169                pkarr.no_default_network();
170                pkarr.bootstrap(&self.testnet.dht.bootstrap)
171            });
172        let relay = unsafe { builder.run() }.await?;
173        self.testnet.pkarr_relays.push(relay);
174        self.temp_dirs.push(temp_dir);
175        Ok(())
176    }
177
178    /// Creates a fixed http relay on port 15412.
179    async fn run_fixed_http_relay(&mut self) -> anyhow::Result<()> {
180        let relay = HttpRelay::builder()
181            .http_port(15412) // Random available port
182            .cors_allow_all(true)
183            .run()
184            .await?;
185        self.testnet.http_relays.push(relay);
186        Ok(())
187    }
188
189    async fn run_fixed_homeserver(&mut self) -> anyhow::Result<()> {
190        let mut config = if let Some(config_path) = &self.homeserver_config {
191            ConfigToml::from_file(config_path)?
192        } else {
193            ConfigToml::default_test_config()
194        };
195        let keypair = pubky_common::crypto::Keypair::from_secret(&[0; 32]);
196        config.pkdns.dht_bootstrap_nodes = Some(
197            self.bootstrap_nodes()
198                .iter()
199                .map(|node| DomainPort::from_str(node).unwrap())
200                .collect(),
201        );
202        config.pkdns.dht_relay_nodes = None;
203        config.drive.icann_listen_socket =
204            SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 6286);
205        config.drive.pubky_listen_socket =
206            SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 6287);
207        config.admin.enabled = true; // Enable admin server for static testnet
208        config.admin.listen_socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 6288);
209        let mock = MockDataDir::new(config, Some(keypair))?;
210
211        let homeserver = HomeserverApp::start_with_mock_data_dir(mock).await?;
212        self.testnet.homeservers.push(homeserver);
213        Ok(())
214    }
215}