noosphere_ns/
name_system.rs

1use crate::{
2    dht::{DhtConfig, DhtError, DhtNode, DhtRecord, NetworkInfo, Peer},
3    utils::make_p2p_address,
4    validator::RecordValidator,
5    DhtClient, PeerId,
6};
7use anyhow::{anyhow, Result};
8use async_trait::async_trait;
9use libp2p::{identity::Keypair, Multiaddr};
10use noosphere_core::data::{Did, LinkRecord};
11use ucan::{crypto::KeyMaterial, store::UcanJwtStore};
12use ucan_key_support::ed25519::Ed25519KeyMaterial;
13
14#[cfg(doc)]
15use cid::Cid;
16
17pub static BOOTSTRAP_PEERS_ADDRESSES: [&str; 1] =
18    ["/ip4/134.122.20.28/tcp/6666/p2p/12D3KooWPyjAB3XWUboGmLLPkR53fTyj4GaNi65RvQ61BVwqV4HG"];
19
20lazy_static! {
21    /// Noosphere Name System's maintained list of peers to
22    /// bootstrap nodes joining the network.
23    pub static ref BOOTSTRAP_PEERS: [Multiaddr; 1] = BOOTSTRAP_PEERS_ADDRESSES.map(|addr| addr.parse().expect("parseable"));
24}
25
26pub trait NameSystemKeyMaterial: KeyMaterial + Clone {
27    fn to_dht_keypair(&self) -> anyhow::Result<Keypair>;
28}
29
30impl NameSystemKeyMaterial for Ed25519KeyMaterial {
31    fn to_dht_keypair(&self) -> anyhow::Result<Keypair> {
32        pub const ED25519_KEY_LENGTH: usize = 32;
33        let mut bytes: [u8; ED25519_KEY_LENGTH] = [0u8; ED25519_KEY_LENGTH];
34        bytes[..ED25519_KEY_LENGTH].copy_from_slice(
35            self.1
36                .ok_or_else(|| anyhow!("Private key required in order to deserialize."))?
37                .as_ref(),
38        );
39        let kp = Keypair::ed25519_from_bytes(&mut bytes)
40            .map_err(|_| anyhow::anyhow!("Could not decode ED25519 key."))?;
41        Ok(kp)
42    }
43}
44
45/// The [NameSystem] is responsible for both propagating and resolving Sphere
46/// DIDs into an authorized UCAN publish token, resolving into a
47/// [Link<MemoIpld>] address for a sphere's content. These records are
48/// propagated and resolved via the Noosphere Name System, a distributed
49/// network, built on [libp2p](https://libp2p.io)'s [Kademlia DHT
50/// specification](https://github.com/libp2p/specs/blob/master/kad-dht/README.md).
51///
52/// Hosted records can be set via [NameSystem::put_record], propagating the
53/// record immediately, and repropagating on a specified interval. Records can
54/// be resolved via [NameSystem::get_record].
55///
56/// See
57/// <https://github.com/subconsciousnetwork/noosphere/blob/main/design/name-system.md>
58/// for the full Noosphere Name System spec.
59pub struct NameSystem {
60    pub(crate) dht: DhtNode,
61}
62
63impl NameSystem {
64    pub fn new<K: NameSystemKeyMaterial, S: UcanJwtStore + 'static>(
65        key_material: &K,
66        dht_config: DhtConfig,
67        store: Option<S>,
68    ) -> Result<Self> {
69        let keypair = key_material.to_dht_keypair()?;
70        let validator = store.map(|s| RecordValidator::new(s));
71
72        Ok(NameSystem {
73            dht: DhtNode::new(keypair, dht_config, validator)?,
74        })
75    }
76}
77
78#[async_trait]
79impl DhtClient for NameSystem {
80    /// Returns current network information for this node.
81    async fn network_info(&self) -> Result<NetworkInfo> {
82        self.dht.network_info().await.map_err(|e| e.into())
83    }
84
85    /// Returns current network information for this node.
86    fn peer_id(&self) -> &PeerId {
87        self.dht.peer_id()
88    }
89
90    /// Adds peers to connect to. Unless bootstrapping a network, at least one
91    /// peer is needed.
92    async fn add_peers(&self, peers: Vec<Multiaddr>) -> Result<()> {
93        self.dht.add_peers(peers).await.map_err(|e| e.into())
94    }
95
96    /// Returns current network information for this node.
97    async fn peers(&self) -> Result<Vec<Peer>> {
98        self.dht.peers().await.map_err(|e| e.into())
99    }
100
101    /// Starts listening for connections on provided address.
102    async fn listen(&self, listening_address: Multiaddr) -> Result<Multiaddr> {
103        self.dht
104            .listen(listening_address)
105            .await
106            .map_err(|e| e.into())
107    }
108
109    /// Stops listening for connections on provided address.
110    async fn stop_listening(&self) -> Result<()> {
111        self.dht.stop_listening().await.map_err(|e| e.into())
112    }
113
114    /// Connects to peers provided in `add_peers`.
115    async fn bootstrap(&self) -> Result<()> {
116        self.dht.bootstrap().await.map_err(|e| e.into())
117    }
118
119    /// Returns the listening addresses of this node.
120    async fn address(&self) -> Result<Option<Multiaddr>> {
121        let mut addresses = self
122            .dht
123            .addresses()
124            .await
125            .map_err(<DhtError as Into<anyhow::Error>>::into)?;
126        if !addresses.is_empty() {
127            let peer_id = self.peer_id().to_owned();
128            let address = make_p2p_address(addresses.swap_remove(0), peer_id);
129            Ok(Some(address))
130        } else {
131            Ok(None)
132        }
133    }
134
135    async fn put_record(&self, record: LinkRecord, quorum: usize) -> Result<()> {
136        let identity = record.to_sphere_identity();
137        let record_bytes: Vec<u8> = record.try_into()?;
138        match self
139            .dht
140            .put_record(identity.as_bytes(), &record_bytes, quorum)
141            .await
142        {
143            Ok(_) => Ok(()),
144            Err(e) => Err(anyhow!(e.to_string())),
145        }
146    }
147
148    async fn get_record(&self, identity: &Did) -> Result<Option<LinkRecord>> {
149        match self.dht.get_record(identity.as_bytes()).await {
150            Ok(DhtRecord { key: _, value }) => match value {
151                Some(value) => Ok(Some(LinkRecord::try_from(value)?)),
152                None => Ok(None),
153            },
154            Err(e) => Err(anyhow!(e.to_string())),
155        }
156    }
157}
158
159#[cfg(test)]
160mod test {
161    use super::*;
162    use noosphere_core::authority::generate_ed25519_key;
163
164    #[test]
165    fn bootstrap_peers_parseable() {
166        // Force getting the lazy static ensuring the addresses
167        // are valid Multiaddrs.
168        assert_eq!(BOOTSTRAP_PEERS.len(), 1);
169    }
170
171    use crate::name_resolver_tests;
172    async fn before_name_resolver_tests() -> Result<NameSystem> {
173        let ns = {
174            let key_material = generate_ed25519_key();
175            let store = SphereDb::new(&MemoryStorage::default()).await.unwrap();
176            let ns = NameSystemBuilder::default()
177                .ucan_store(store)
178                .key_material(&key_material)
179                .listening_port(0)
180                .use_test_config()
181                .build()
182                .await
183                .unwrap();
184            ns.bootstrap().await.unwrap();
185            ns
186        };
187        Ok(ns)
188    }
189    name_resolver_tests!(NameSystem, before_name_resolver_tests);
190
191    use crate::dht_client_tests;
192    use crate::{utils::wait_for_peers, NameSystemBuilder};
193    use noosphere_storage::{MemoryStorage, SphereDb};
194    use std::sync::Arc;
195    use tokio::sync::Mutex;
196
197    /// This struct is used to persist non-Client objects, like
198    /// the name system and/or server, through the duration
199    /// of each test.
200    struct DataPlaceholder {
201        _bootstrap: NameSystem,
202        _ns: Arc<Mutex<NameSystem>>,
203    }
204
205    async fn before_each() -> Result<(DataPlaceholder, Arc<Mutex<NameSystem>>)> {
206        let (bootstrap, bootstrap_address) = {
207            let key_material = generate_ed25519_key();
208            let store = SphereDb::new(&MemoryStorage::default()).await.unwrap();
209            let ns = NameSystemBuilder::default()
210                .ucan_store(store)
211                .key_material(&key_material)
212                .listening_port(0)
213                .use_test_config()
214                .build()
215                .await
216                .unwrap();
217            ns.bootstrap().await.unwrap();
218            let address = ns.address().await?.unwrap();
219            (ns, address)
220        };
221
222        let ns = {
223            let key_material = generate_ed25519_key();
224            let store = SphereDb::new(&MemoryStorage::default()).await.unwrap();
225            let ns = NameSystemBuilder::default()
226                .ucan_store(store)
227                .key_material(&key_material)
228                .bootstrap_peers(&[bootstrap_address.clone()])
229                .use_test_config()
230                .build()
231                .await
232                .unwrap();
233            ns.bootstrap().await.unwrap();
234            wait_for_peers::<NameSystem>(&ns, 1).await?;
235            ns
236        };
237
238        let client = Arc::new(Mutex::new(ns));
239        // To align with implementations with discrete server/client
240        // objects, we clone the NameSystem itself as the persistent
241        // reference.
242        let reference = client.clone();
243        let data = DataPlaceholder {
244            _ns: reference,
245            _bootstrap: bootstrap,
246        };
247        Ok((data, client))
248    }
249
250    dht_client_tests!(NameSystem, before_each, DataPlaceholder);
251}