noosphere_ns/
builder.rs

1use crate::{dht::DhtConfig, name_system::NameSystem, DhtClient, NameSystemKeyMaterial};
2use anyhow::{anyhow, Result};
3use libp2p::{self, Multiaddr};
4use std::net::Ipv4Addr;
5use ucan::store::UcanJwtStore;
6
7#[cfg(doc)]
8use libp2p::kad::KademliaConfig;
9
10/// [NameSystemBuilder] is an alternate interface for
11/// creating a new [NameSystem]. `key_material` and `store`
12/// must be provided.
13///
14/// # Examples
15///
16/// ```
17/// use noosphere_core::authority::generate_ed25519_key;
18/// use noosphere_storage::{SphereDb, MemoryStorage};
19/// use noosphere_ns::{BOOTSTRAP_PEERS, NameSystem, DhtClient, NameSystemBuilder};
20/// use ucan_key_support::ed25519::Ed25519KeyMaterial;
21/// use tokio;
22///
23/// #[tokio::main(flavor = "multi_thread")]
24/// async fn main() {
25///     let key_material = generate_ed25519_key();
26///     let store = SphereDb::new(&MemoryStorage::default()).await.unwrap();
27///
28///     let ns = NameSystemBuilder::default()
29///         .ucan_store(store)
30///         .key_material(&key_material)
31///         .listening_port(30000)
32///         .bootstrap_peers(&BOOTSTRAP_PEERS[..])
33///         .build().await.unwrap();
34///     ns.bootstrap().await.unwrap();
35/// }
36/// ```
37pub struct NameSystemBuilder<K, S>
38where
39    K: NameSystemKeyMaterial + 'static,
40    S: UcanJwtStore + 'static,
41{
42    bootstrap_peers: Option<Vec<Multiaddr>>,
43    listening_address: Option<Multiaddr>,
44    dht_config: DhtConfig,
45    key_material: Option<K>,
46    ucan_store: Option<S>,
47}
48
49impl<K, S> NameSystemBuilder<K, S>
50where
51    K: NameSystemKeyMaterial + 'static,
52    S: UcanJwtStore + 'static,
53{
54    /// If bootstrap peers are provided, how often,
55    /// in seconds, should the bootstrap process execute
56    /// to keep routing tables fresh.
57    pub fn bootstrap_interval(mut self, interval: u64) -> Self {
58        self.dht_config.bootstrap_interval = interval;
59        self
60    }
61
62    /// Peer addresses to query to update routing tables
63    /// during bootstrap. A standalone bootstrap node would
64    /// have this field empty.
65    pub fn bootstrap_peers(mut self, peers: &[Multiaddr]) -> Self {
66        self.bootstrap_peers = Some(peers.to_owned());
67        self
68    }
69
70    /// Public/private keypair for DHT node.
71    pub fn key_material(mut self, key_material: &K) -> Self {
72        self.key_material = Some(key_material.to_owned());
73        self
74    }
75
76    /// Port to listen for incoming TCP connections. If not specified,
77    /// an open port is automatically chosen.
78    pub fn listening_port(mut self, port: u16) -> Self {
79        let mut address = Multiaddr::empty();
80        address.push(libp2p::multiaddr::Protocol::Ip4(Ipv4Addr::new(
81            127, 0, 0, 1,
82        )));
83        address.push(libp2p::multiaddr::Protocol::Tcp(port));
84        self.listening_address = Some(address);
85        self
86    }
87
88    /// How frequently, in seconds, the DHT attempts to
89    /// dial peers found in its kbucket. Outside of tests,
90    /// should not be lower than 5 seconds.
91    pub fn peer_dialing_interval(mut self, interval: u64) -> Self {
92        self.dht_config.peer_dialing_interval = interval;
93        self
94    }
95
96    /// How long, in seconds, published records are replicated to
97    /// peers. Should be significantly shorter than `record_ttl`.
98    /// See [KademliaConfig::set_publication_interval] and [KademliaConfig::set_provider_publication_interval].
99    pub fn publication_interval(mut self, interval: u32) -> Self {
100        self.dht_config.publication_interval = interval;
101        self
102    }
103
104    /// How long, in seconds, until a network query times out.
105    pub fn query_timeout(mut self, timeout: u32) -> Self {
106        self.dht_config.query_timeout = timeout;
107        self
108    }
109
110    pub fn ucan_store(mut self, store: S) -> Self {
111        self.ucan_store = Some(store);
112        self
113    }
114
115    /// How long, in seconds, records remain valid for. Should be significantly
116    /// longer than `publication_interval`.
117    /// See [KademliaConfig::set_record_ttl] and [KademliaConfig::set_provider_record_ttl].
118    pub fn record_ttl(mut self, interval: u32) -> Self {
119        self.dht_config.record_ttl = interval;
120        self
121    }
122
123    /// How long, in seconds, stored records are replicated to
124    /// peers. Should be significantly shorter than `publication_interval`.
125    /// See [KademliaConfig::set_replication_interval].
126    pub fn replication_interval(mut self, interval: u32) -> Self {
127        self.dht_config.replication_interval = interval;
128        self
129    }
130
131    /// Build a [NameSystem] based off of the provided configuration.
132    pub async fn build(mut self) -> Result<NameSystem> {
133        let key_material = self
134            .key_material
135            .take()
136            .ok_or_else(|| anyhow!("key_material required."))?;
137        let ucan_store = self
138            .ucan_store
139            .ok_or_else(|| anyhow!("ucan_store is required"))?;
140        let ns = NameSystem::new(&key_material, self.dht_config.clone(), Some(ucan_store))?;
141
142        if let Some(listening_address) = self.listening_address {
143            ns.listen(listening_address).await?;
144        }
145
146        if let Some(bootstrap_peers) = self.bootstrap_peers {
147            ns.add_peers(bootstrap_peers).await?;
148        }
149
150        Ok(ns)
151    }
152
153    #[cfg(test)]
154    /// Helper method to configure a NameSystem instance to
155    /// use test-friendly values when running in CI.
156    pub fn use_test_config(mut self) -> Self {
157        self.dht_config.peer_dialing_interval = 1;
158        self
159    }
160}
161
162impl<K, S> Default for NameSystemBuilder<K, S>
163where
164    K: NameSystemKeyMaterial + 'static,
165    S: UcanJwtStore + 'static,
166{
167    fn default() -> Self {
168        Self {
169            bootstrap_peers: None,
170            dht_config: DhtConfig::default(),
171            key_material: None,
172            listening_address: None,
173            ucan_store: None,
174        }
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181    use libp2p::PeerId;
182    use noosphere_core::authority::generate_ed25519_key;
183    use noosphere_storage::{MemoryStorage, SphereDb};
184    use ucan_key_support::ed25519::Ed25519KeyMaterial;
185
186    #[tokio::test]
187    async fn test_name_system_builder() -> Result<(), anyhow::Error> {
188        let key_material = generate_ed25519_key();
189        let peer_id = {
190            let keypair = key_material.to_dht_keypair()?;
191            PeerId::from(keypair.public())
192        };
193        let store = SphereDb::new(&MemoryStorage::default()).await.unwrap();
194        let bootstrap_peers: Vec<Multiaddr> = vec![
195            "/ip4/127.0.0.50/tcp/33333/p2p/12D3KooWH8WgH9mgbMXrKX4veokUznvEn6Ycwg4qaGNi83nLkoUK"
196                .parse()?,
197            "/ip4/127.0.0.50/tcp/33334/p2p/12D3KooWMWo6tNGRx1G4TNqvr4SnHyVXSReC3tdX6zoJothXxV2c"
198                .parse()?,
199        ];
200
201        let ns = NameSystemBuilder::default()
202            .listening_port(30000)
203            .ucan_store(store.clone())
204            .key_material(&key_material)
205            .bootstrap_peers(&bootstrap_peers)
206            .bootstrap_interval(33)
207            .peer_dialing_interval(11)
208            .query_timeout(22)
209            .publication_interval(60 * 60 * 24 + 1)
210            .replication_interval(60 * 60 + 1)
211            .record_ttl(60 * 60 * 24 * 3 + 1)
212            .build()
213            .await?;
214
215        assert_eq!(ns.dht.peer_id(), &peer_id);
216        assert_eq!(
217            ns.address().await?.unwrap(),
218            format!("/ip4/127.0.0.1/tcp/30000/p2p/{}", peer_id).parse()?
219        );
220        let dht_config = ns.dht.config();
221        assert_eq!(dht_config.bootstrap_interval, 33);
222        assert_eq!(dht_config.peer_dialing_interval, 11);
223        assert_eq!(dht_config.query_timeout, 22);
224        assert_eq!(dht_config.publication_interval, 60 * 60 * 24 + 1);
225        assert_eq!(dht_config.replication_interval, 60 * 60 + 1);
226        assert_eq!(dht_config.record_ttl, 60 * 60 * 24 * 3 + 1);
227
228        if NameSystemBuilder::<Ed25519KeyMaterial, _>::default()
229            .ucan_store(store.clone())
230            .build()
231            .await
232            .is_ok()
233        {
234            panic!("key_material required.");
235        }
236        if NameSystemBuilder::<Ed25519KeyMaterial, SphereDb<MemoryStorage>>::default()
237            .key_material(&key_material)
238            .build()
239            .await
240            .is_ok()
241        {
242            panic!("ucan_store required.");
243        }
244        Ok(())
245    }
246}