Skip to main content

pkarr_client/client/
blocking.rs

1use ntimestamp::Timestamp;
2
3use crate::mainline;
4use crate::{Cache, PublicKey, SignedPacket};
5
6use super::{Client, PublishError};
7
8impl Client {
9    /// Returns a blocking (synchronous ) version of [Client].
10    pub fn as_blocking(&self) -> ClientBlocking {
11        ClientBlocking(self.clone())
12    }
13}
14
15/// A blocking (synchronous) version of [Client].
16#[derive(Clone, Debug)]
17pub struct ClientBlocking(Client);
18
19impl ClientBlocking {
20    // === Getters ===
21
22    /// Returns a reference to the internal cache.
23    pub fn cache(&self) -> Option<&dyn Cache> {
24        self.0.cache()
25    }
26
27    /// Returns a reference to the internal [mainline::Dht] node.
28    ///
29    /// Gives you access to methods like [mainline::Dht::info],
30    /// [mainline::Dht::bootstrapped], and [mainline::Dht::to_bootstrap]
31    /// among the rest of the API.
32    #[cfg(dht)]
33    pub fn dht(&self) -> Option<mainline::Dht> {
34        self.0.dht()
35    }
36
37    // === Publish ===
38
39    /// Publishes a [SignedPacket] to the [mainline] Dht and or [Relays](https://pkarr.org/relays).
40    ///
41    /// # Lost Update Problem
42    ///
43    /// Mainline DHT and remote relays form a distributed network, and like all distributed networks,
44    /// it is vulnerable to [Write–write conflict](https://en.wikipedia.org/wiki/Write-write_conflict).
45    ///
46    /// ## Read first
47    ///
48    /// To mitigate the risk of lost updates, you should call the [Self::resolve_most_recent] method
49    /// then start authoring the new [SignedPacket] based on the most recent as in the following example:
50    ///
51    ///```rust
52    /// use pkarr::{Client, SignedPacket, Keypair};
53    /// // For local testing
54    /// use pkarr::mainline::Testnet;
55    ///
56    /// fn run() -> anyhow::Result<()> {
57    ///     let testnet = Testnet::new(3)?;
58    ///     let client = Client::builder()
59    ///         // Disable the default network settings (builtin relays and mainline bootstrap nodes).
60    ///         .no_default_network()
61    ///         .bootstrap(&testnet.bootstrap)
62    ///         .build()?
63    ///         .as_blocking();
64    ///
65    ///     let keypair = Keypair::random();
66    ///
67    ///     let (signed_packet, cas) = if let Some(most_recent) = client
68    ///         .resolve_most_recent(&keypair.public_key())
69    ///     {
70    ///
71    ///         let mut builder = SignedPacket::builder();
72    ///
73    ///         // 1. Optionally inherit all or some of the existing records.
74    ///         for record in most_recent.all_resource_records() {
75    ///             let name = record.name.to_string();
76    ///
77    ///             if name != "foo" && name != "sercert" {
78    ///                 builder = builder.record(record.clone());
79    ///             }
80    ///         };
81    ///
82    ///         // 2. Optionally add more new records.
83    ///         let signed_packet = builder
84    ///             .txt("foo".try_into()?, "bar".try_into()?, 30)
85    ///             .a("secret".try_into()?, 42.into(), 30)
86    ///             .sign(&keypair)?;
87    ///
88    ///         (
89    ///             signed_packet,
90    ///             // 3. Use the most recent [SignedPacket::timestamp] as a `CAS`.
91    ///             Some(most_recent.timestamp())
92    ///         )
93    ///     } else {
94    ///         (
95    ///             SignedPacket::builder()
96    ///                 .txt("foo".try_into()?, "bar".try_into()?, 30)
97    ///                 .a("secret".try_into()?, 42.into(), 30)
98    ///                 .sign(&keypair)?,
99    ///             None
100    ///         )
101    ///     };
102    ///
103    ///     client.publish(&signed_packet, cas)?;
104    ///
105    ///     Ok(())
106    /// }
107    /// ```
108    ///
109    /// ## Errors
110    ///
111    /// This method may return on of these errors:
112    ///
113    /// 1. [super::QueryError]: when the query fails, and you need to retry or debug the network.
114    /// 2. [super::ConcurrencyError]: when an write conflict (or the risk of it) is detedcted.
115    ///
116    /// If you get a [super::ConcurrencyError]; you should resolver the most recent packet again,
117    /// and repeat the steps in the previous example.
118    pub fn publish(
119        &self,
120        signed_packet: &SignedPacket,
121        cas: Option<Timestamp>,
122    ) -> Result<(), PublishError> {
123        futures_lite::future::block_on(self.0.publish(signed_packet, cas))
124    }
125
126    // === Resolve ===
127
128    /// Returns a [SignedPacket] from the cache even if it is expired.
129    /// If there is no packet in the cache, or if the cached packet is expired,
130    /// it will make a DHT query in a background query and caches any more recent packets it receives.
131    ///
132    /// If you want to get the most recent version of a [SignedPacket],
133    /// you should use [Self::resolve_most_recent].
134    pub fn resolve(&self, public_key: &PublicKey) -> Option<SignedPacket> {
135        futures_lite::future::block_on(self.0.resolve(public_key))
136    }
137
138    /// Returns the most recent [SignedPacket] found after querying all
139    /// [mainline] Dht nodes and or [Relays](https:://pkarr.org/relays).
140    ///
141    /// Useful if you want to read the most recent packet before publishing
142    /// a new packet.
143    ///
144    /// This is a best effort, and doesn't guarantee consistency.
145    pub fn resolve_most_recent(&self, public_key: &PublicKey) -> Option<SignedPacket> {
146        futures_lite::future::block_on(self.0.resolve_most_recent(public_key))
147    }
148}