Skip to main content

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