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}