p2panda_net/address_book/
api.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use std::collections::HashSet;
4use std::sync::Arc;
5
6use p2panda_discovery::address_book::{BoxedAddressBookStore, BoxedError};
7use ractor::{ActorRef, call, cast};
8use thiserror::Error;
9use tokio::sync::RwLock;
10
11use crate::address_book::Builder;
12use crate::address_book::actor::ToAddressBookActor;
13use crate::address_book::report::ConnectionOutcome;
14use crate::addrs::{NodeInfo, NodeInfoError, TransportInfo};
15use crate::watchers::{UpdatesOnly, WatcherReceiver};
16use crate::{NodeId, TopicId};
17
18/// Manage node information, bootstraps and their associated transport addresses and topics.
19///
20/// ## Example
21///
22/// To help an application to bootstrap into the network, it is possible to manually add node
23/// information and manage associated topics for nodes directly on the address book using
24/// [`AddressBook::insert_node_info`]:
25///
26/// ```rust
27/// # use std::error::Error;
28/// #
29/// # #[tokio::main]
30/// # async fn main() -> Result<(), Box<dyn Error>> {
31/// # use p2panda_net::{AddressBook, Endpoint};
32/// # use p2panda_net::addrs::NodeInfo;
33/// #
34/// let address_book = AddressBook::builder().spawn().await?;
35///
36/// let bootstrap_node = {
37///     let node_id = "c0f3ce745cee96e1e9c01a20746cd503bb2199c2459d8ff8697f5edb30569101"
38///         .parse()
39///         .expect("valid hex-encoded Ed25519 public key");
40///     let relay_url = "https://my.relay.org".parse().expect("valid relay url");
41///
42///     let endpoint_addr = iroh::EndpointAddr::new(node_id)
43///        .with_relay_url(relay_url);
44///
45///     NodeInfo::from(endpoint_addr).bootstrap()
46/// };
47///
48/// address_book.insert_node_info(bootstrap_node).await?;
49/// #
50/// # Ok(())
51/// # }
52/// ```
53///
54/// ## Topic Discovery and Resolving Transport Info
55///
56/// The address book itself is populated with transport information and associated node topics by
57/// two "discovery" services:
58///
59/// 1. [`MdnsDiscovery`](crate::MdnsDiscovery): Resolve addresses of nearby devices on the
60///    local-area network.
61/// 2. [`Discovery`](crate::Discovery): Resolve addresses and confidentially exchange topics using
62///    random-walk strategy, exploring the network.
63#[derive(Clone)]
64pub struct AddressBook {
65    pub(super) inner: Arc<RwLock<Inner>>,
66}
67
68pub(super) struct Inner {
69    pub(super) actor_ref: Option<ActorRef<ToAddressBookActor>>,
70}
71
72impl AddressBook {
73    pub(crate) fn new(actor_ref: Option<ActorRef<ToAddressBookActor>>) -> Self {
74        Self {
75            inner: Arc::new(RwLock::new(Inner { actor_ref })),
76        }
77    }
78
79    pub fn builder() -> Builder {
80        Builder::new()
81    }
82
83    /// Returns information about a node.
84    ///
85    /// Returns `None` if no information was found for this node.
86    pub async fn node_info(&self, node_id: NodeId) -> Result<Option<NodeInfo>, AddressBookError> {
87        let inner = self.inner.read().await;
88        let result = call!(
89            inner.actor_ref.as_ref().expect("actor spawned in builder"),
90            ToAddressBookActor::NodeInfo,
91            node_id
92        )
93        .map_err(Box::new)?;
94        Ok(result)
95    }
96
97    /// Inserts or updates node information into address book.
98    ///
99    /// Use this method if adding node information from a local configuration or trusted, external
100    /// source, etc.
101    ///
102    /// Returns `true` if entry got newly inserted or `false` if existing entry was updated.
103    /// Previous entries are simply overwritten. Entries with attached transport information get
104    /// checked against authenticity and throw an error otherwise.
105    pub async fn insert_node_info(&self, node_info: NodeInfo) -> Result<bool, AddressBookError> {
106        let inner = self.inner.read().await;
107        let result = call!(
108            inner.actor_ref.as_ref().expect("actor spawned in builder"),
109            ToAddressBookActor::InsertNodeInfo,
110            node_info
111        )
112        .map_err(Box::new)??;
113        Ok(result)
114    }
115
116    /// Inserts or updates attached transport info for a node. Use this method if adding transport
117    /// information from an untrusted source.
118    ///
119    /// Transport information is usually exchanged as part of a discovery protocol and should be
120    /// considered untrusted.
121    ///
122    /// This method checks if the given information is authentic and uses a timestamp to apply a
123    /// "last write wins" rule. It retuns `true` if the given entry overwritten the previous one or
124    /// `false` if the previous entry is already the latest.
125    ///
126    /// Local data of the node information stay untouched if they already exist, only the
127    /// "transports" aspect gets inserted / updated.
128    pub async fn insert_transport_info(
129        &self,
130        node_id: NodeId,
131        transport_info: TransportInfo,
132    ) -> Result<bool, AddressBookError> {
133        let inner = self.inner.read().await;
134        let result = call!(
135            inner.actor_ref.as_ref().expect("actor spawned in builder"),
136            ToAddressBookActor::InsertTransportInfo,
137            node_id,
138            transport_info
139        )
140        .map_err(Box::new)??;
141        Ok(result)
142    }
143
144    pub async fn node_infos_by_topics(
145        &self,
146        topics: impl IntoIterator<Item = TopicId>,
147    ) -> Result<Vec<NodeInfo>, AddressBookError> {
148        let inner = self.inner.read().await;
149        let result = call!(
150            inner.actor_ref.as_ref().expect("actor spawned in builder"),
151            ToAddressBookActor::NodeInfosByTopics,
152            topics.into_iter().collect()
153        )
154        .map_err(Box::new)?;
155        Ok(result)
156    }
157
158    pub async fn set_topics(
159        &self,
160        node_id: NodeId,
161        topics: impl IntoIterator<Item = TopicId>,
162    ) -> Result<(), AddressBookError> {
163        let inner = self.inner.read().await;
164        cast!(
165            inner.actor_ref.as_ref().expect("actor spawned in builder"),
166            ToAddressBookActor::SetTopics(node_id, topics.into_iter().collect())
167        )
168        .map_err(Box::new)?;
169        Ok(())
170    }
171
172    pub async fn add_topic(&self, node_id: NodeId, topic: TopicId) -> Result<(), AddressBookError> {
173        let inner = self.inner.read().await;
174        cast!(
175            inner.actor_ref.as_ref().expect("actor spawned in builder"),
176            ToAddressBookActor::AddTopic(node_id, topic)
177        )
178        .map_err(Box::new)?;
179        Ok(())
180    }
181
182    pub async fn remove_topic(
183        &self,
184        node_id: NodeId,
185        topic: TopicId,
186    ) -> Result<(), AddressBookError> {
187        let inner = self.inner.read().await;
188        cast!(
189            inner.actor_ref.as_ref().expect("actor spawned in builder"),
190            ToAddressBookActor::RemoveTopic(node_id, topic)
191        )
192        .map_err(Box::new)?;
193        Ok(())
194    }
195
196    /// Subscribes to channel informing us about node info changes for a specific node.
197    pub async fn watch_node_info(
198        &self,
199        node_id: NodeId,
200        updates_only: UpdatesOnly,
201    ) -> Result<WatcherReceiver<Option<NodeInfo>>, AddressBookError> {
202        let inner = self.inner.read().await;
203        let result = call!(
204            inner.actor_ref.as_ref().expect("actor spawned in builder"),
205            ToAddressBookActor::WatchNodeInfo,
206            node_id,
207            updates_only
208        )
209        .map_err(Box::new)?;
210        Ok(result)
211    }
212
213    /// Subscribes to channel informing us about changes of the set of nodes interested in a topic.
214    pub async fn watch_topic(
215        &self,
216        topic_id: TopicId,
217        updates_only: UpdatesOnly,
218    ) -> Result<WatcherReceiver<HashSet<NodeId>>, AddressBookError> {
219        let inner = self.inner.read().await;
220        let result = call!(
221            inner.actor_ref.as_ref().expect("actor spawned in builder"),
222            ToAddressBookActor::WatchTopic,
223            topic_id,
224            updates_only
225        )
226        .map_err(Box::new)?;
227        Ok(result)
228    }
229
230    /// Subscribes to channel informing us about topic changes for a particular node.
231    pub async fn watch_node_topics(
232        &self,
233        node_id: NodeId,
234        updates_only: UpdatesOnly,
235    ) -> Result<WatcherReceiver<HashSet<TopicId>>, AddressBookError> {
236        let inner = self.inner.read().await;
237        let result = call!(
238            inner.actor_ref.as_ref().expect("actor spawned in builder"),
239            ToAddressBookActor::WatchNodeTopics,
240            node_id,
241            updates_only
242        )
243        .map_err(Box::new)?;
244        Ok(result)
245    }
246
247    /// Report outcomes of incoming or outgoing connections.
248    ///
249    /// This helps measuring the "quality" of nodes which will be recorded in the address book.
250    pub async fn report(
251        &self,
252        node_id: NodeId,
253        connection_outcome: ConnectionOutcome,
254    ) -> Result<(), AddressBookError> {
255        let inner = self.inner.read().await;
256        cast!(
257            inner.actor_ref.as_ref().expect("actor spawned in builder"),
258            ToAddressBookActor::Report(node_id, connection_outcome)
259        )
260        .map_err(Box::new)?;
261        Ok(())
262    }
263
264    pub(crate) async fn store(
265        &self,
266    ) -> Result<BoxedAddressBookStore<NodeId, NodeInfo>, AddressBookError> {
267        let inner = self.inner.read().await;
268        let result = call!(
269            inner.actor_ref.as_ref().expect("actor spawned in builder"),
270            ToAddressBookActor::Store
271        )
272        .map_err(Box::new)?;
273        Ok(result)
274    }
275}
276
277impl Drop for Inner {
278    fn drop(&mut self) {
279        if let Some(actor_ref) = self.actor_ref.take() {
280            actor_ref.stop(None);
281        }
282    }
283}
284
285#[derive(Debug, Error)]
286pub enum AddressBookError {
287    /// Spawning the internal actor failed.
288    #[error(transparent)]
289    ActorSpawn(#[from] ractor::SpawnErr),
290
291    /// Spawning the internal actor as a child actor of a supervisor failed.
292    #[cfg(feature = "supervisor")]
293    #[error(transparent)]
294    ActorLinkedSpawn(#[from] crate::supervisor::SupervisorError),
295
296    /// Messaging with internal actor via RPC failed.
297    #[error(transparent)]
298    ActorRpc(#[from] Box<ractor::RactorErr<ToAddressBookActor>>),
299
300    /// Address book store failed.
301    #[error(transparent)]
302    Store(#[from] BoxedError),
303
304    /// Invalid node info provided.
305    #[error(transparent)]
306    NodeInfo(#[from] NodeInfoError),
307}