Skip to main content

calimero_context_primitives/client/external/
config.rs

1use core::future::Future;
2
3use calimero_context_config::client::env::config::ContextConfig;
4use calimero_context_config::repr::{Repr, ReprBytes, ReprTransmute};
5use calimero_context_config::types::{self as types, BlockHeight, Capability, SignedRevealPayload};
6use calimero_primitives::application::{Application, ApplicationBlob};
7use calimero_primitives::blobs::BlobId;
8use calimero_primitives::context::ContextId;
9use calimero_primitives::identity::{PrivateKey, PublicKey};
10use eyre::{bail, OptionExt};
11
12use super::ExternalClient;
13
14const MAX_RETRIES: u8 = 3;
15
16#[derive(Debug)]
17pub struct ExternalConfigClient<'a> {
18    nonce: Option<u64>,
19    client: &'a ExternalClient<'a>,
20}
21
22impl ExternalClient<'_> {
23    #[must_use]
24    pub const fn config(&self) -> ExternalConfigClient<'_> {
25        ExternalConfigClient {
26            nonce: None,
27            client: self,
28        }
29    }
30}
31
32impl ExternalConfigClient<'_> {
33    pub async fn fetch_nonce(&self, public_key: &PublicKey) -> eyre::Result<u64> {
34        let client = self.client.query::<ContextConfig>(
35            self.client.config.protocol.as_ref().into(),
36            self.client.config.network_id.as_ref().into(),
37            self.client.config.contract_id.as_ref().into(),
38        );
39
40        let context_id = self.client.context_id.rt().expect("infallible conversion");
41        let public_key = public_key.rt().expect("infallible conversion");
42
43        let nonce = client
44            .fetch_nonce(context_id, public_key)
45            .await?
46            .ok_or_eyre("not a member of the context")?;
47
48        Ok(nonce)
49    }
50
51    async fn with_nonce<T, E, F>(
52        &mut self,
53        public_key: &PublicKey,
54        f: impl Fn(u64) -> F,
55    ) -> eyre::Result<T>
56    where
57        E: Into<eyre::Report>,
58        F: Future<Output = Result<T, E>>,
59    {
60        let retries = MAX_RETRIES + u8::from(self.nonce.is_none());
61
62        for _ in 0..=retries {
63            let mut error = None;
64
65            if let Some(nonce) = self.nonce {
66                match f(nonce).await {
67                    Ok(value) => return Ok(value),
68                    Err(err) => error = Some(err),
69                }
70            }
71
72            let old = self.nonce;
73
74            self.nonce = Some(self.fetch_nonce(public_key).await?);
75
76            if let Some(err) = error {
77                if old == self.nonce {
78                    return Err(err.into());
79                }
80            }
81        }
82
83        bail!("max retries exceeded");
84    }
85
86    pub async fn add_context(
87        &self,
88        context_secret: &PrivateKey,
89        identity: &PublicKey,
90        application: &Application,
91    ) -> eyre::Result<()> {
92        let client = self.client.mutate::<ContextConfig>(
93            self.client.config.protocol.as_ref().into(),
94            self.client.config.network_id.as_ref().into(),
95            self.client.config.contract_id.as_ref().into(),
96        );
97
98        client
99            .add_context(
100                self.client.context_id.rt().expect("infallible conversion"),
101                identity.rt().expect("infallible conversion"),
102                types::Application::new(
103                    application.id.rt().expect("infallible conversion"),
104                    application
105                        .blob
106                        .bytecode
107                        .rt()
108                        .expect("infallible conversion"),
109                    application.size,
110                    types::ApplicationSource(application.source.to_string().into()),
111                    types::ApplicationMetadata(Repr::new(application.metadata.as_slice().into())),
112                ),
113            )
114            .send(**context_secret, 0)
115            .await?;
116
117        Ok(())
118    }
119
120    pub async fn update_application(
121        &mut self,
122        public_key: &PublicKey,
123        application: &Application,
124    ) -> eyre::Result<()> {
125        let identity = self
126            .client
127            .context_client()
128            .get_identity(&self.client.context_id, public_key)?
129            .ok_or_eyre("identity not found")?;
130
131        let private_key = identity.private_key()?;
132
133        self.with_nonce(public_key, async |nonce| {
134            let client = self.client.mutate::<ContextConfig>(
135                self.client.config.protocol.as_ref().into(),
136                self.client.config.network_id.as_ref().into(),
137                self.client.config.contract_id.as_ref().into(),
138            );
139
140            client
141                .update_application(
142                    self.client.context_id.rt().expect("infallible conversion"),
143                    types::Application::new(
144                        application.id.rt().expect("infallible conversion"),
145                        application
146                            .blob
147                            .bytecode
148                            .rt()
149                            .expect("infallible conversion"),
150                        application.size,
151                        types::ApplicationSource(application.source.to_string().into()),
152                        types::ApplicationMetadata(Repr::new(
153                            application.metadata.as_slice().into(),
154                        )),
155                    ),
156                )
157                .send(**private_key, nonce)
158                .await
159        })
160        .await?;
161
162        Ok(())
163    }
164
165    pub async fn add_members(
166        &mut self,
167        public_key: &PublicKey,
168        identities: &[PublicKey],
169    ) -> eyre::Result<()> {
170        let identity = self
171            .client
172            .context_client()
173            .get_identity(&self.client.context_id, public_key)?
174            .ok_or_eyre("identity not found")?;
175
176        let private_key = identity.private_key()?;
177
178        let identities = identities
179            .iter()
180            .map(|e| e.rt())
181            .collect::<Result<Vec<_>, _>>()
182            .expect("infallible conversion");
183
184        self.with_nonce(public_key, async |nonce| {
185            let client = self.client.mutate::<ContextConfig>(
186                self.client.config.protocol.as_ref().into(),
187                self.client.config.network_id.as_ref().into(),
188                self.client.config.contract_id.as_ref().into(),
189            );
190
191            client
192                .add_members(
193                    self.client.context_id.rt().expect("infallible conversion"),
194                    &identities,
195                )
196                .send(**private_key, nonce)
197                .await
198        })
199        .await?;
200
201        Ok(())
202    }
203
204    /// Calls the `commit_invitation` method on the NEAR contract.
205    /// This is the first step of the open invitation flow.
206    pub async fn join_context_commit_invitation(
207        &mut self,
208        public_key: &PublicKey,
209        commitment_hash: String,
210        expiration_block_height: BlockHeight,
211    ) -> eyre::Result<()> {
212        if self.client.config.protocol != "near" {
213            bail!("Only NEAR Protocol currently supports open invitaitons");
214        }
215
216        // At this step the identity should be at the zeroth context:
217        // it should exist on the node, but available to be assigned for a new context.
218        let identity = self
219            .client
220            .context_client()
221            .get_identity(&ContextId::zero(), public_key)?
222            .ok_or_eyre("identity not found")?;
223
224        let private_key = identity.private_key()?;
225
226        let client = self.client.mutate::<ContextConfig>(
227            self.client.config.protocol.as_ref().into(),
228            self.client.config.network_id.as_ref().into(),
229            self.client.config.contract_id.as_ref().into(),
230        );
231
232        let context_id = self.client.context_id.rt().expect("infallible conversion");
233
234        let nonce = 0;
235        let _ignored = client
236            .commit_invitation(context_id, commitment_hash.clone(), expiration_block_height)
237            .send(**private_key, nonce)
238            .await;
239
240        Ok(())
241    }
242
243    /// Calls the `reveal_invitation` method on the NEAR contract.
244    /// This is the second and final step, which adds the new member.
245    pub async fn join_context_reveal_invitation(
246        &mut self,
247        public_key: &PublicKey,
248        payload: SignedRevealPayload,
249    ) -> eyre::Result<()> {
250        if self.client.config.protocol != "near" {
251            bail!("Only NEAR Protocol currently supports open invitaitons");
252        }
253
254        // At this step the identity should be at the zeroth context:
255        // it should exist on the node, but available to be assigned for a new context.
256        let identity = self
257            .client
258            .context_client()
259            .get_identity(&ContextId::zero(), public_key)?
260            .ok_or_eyre("identity not found")?;
261
262        let private_key = identity.private_key()?;
263
264        let client = self.client.mutate::<ContextConfig>(
265            self.client.config.protocol.as_ref().into(),
266            self.client.config.network_id.as_ref().into(),
267            self.client.config.contract_id.as_ref().into(),
268        );
269
270        let context_id = self.client.context_id.rt().expect("infallible conversion");
271
272        let nonce = 0;
273        // Similar to commit, this is a public method call sent by the relayer.
274        let _ignored = client
275            .reveal_invitation(context_id, payload.clone())
276            .send(**private_key, nonce)
277            .await;
278
279        Ok(())
280    }
281
282    pub async fn remove_members(
283        &mut self,
284        public_key: &PublicKey,
285        identities: &[PublicKey],
286    ) -> eyre::Result<()> {
287        let identity = self
288            .client
289            .context_client()
290            .get_identity(&self.client.context_id, public_key)?
291            .ok_or_eyre("identity not found")?;
292
293        let private_key = identity.private_key()?;
294
295        let identities = identities
296            .iter()
297            .map(|e| e.rt())
298            .collect::<Result<Vec<_>, _>>()
299            .expect("infallible conversion");
300
301        self.with_nonce(public_key, async |nonce| {
302            let client = self.client.mutate::<ContextConfig>(
303                self.client.config.protocol.as_ref().into(),
304                self.client.config.network_id.as_ref().into(),
305                self.client.config.contract_id.as_ref().into(),
306            );
307
308            client
309                .remove_members(
310                    self.client.context_id.rt().expect("infallible conversion"),
311                    &identities,
312                )
313                .send(**private_key, nonce)
314                .await
315        })
316        .await?;
317
318        Ok(())
319    }
320
321    pub async fn grant(
322        &mut self,
323        public_key: &PublicKey,
324        capabilities: &[(PublicKey, Capability)],
325    ) -> eyre::Result<()> {
326        let identity = self
327            .client
328            .context_client()
329            .get_identity(&self.client.context_id, public_key)?
330            .ok_or_eyre("identity not found")?;
331
332        let private_key = identity.private_key()?;
333
334        let capabilities = capabilities
335            .iter()
336            .map(|(who, cap)| who.rt().map(|who| (who, *cap)))
337            .collect::<Result<Vec<_>, _>>()
338            .expect("infallible conversion");
339
340        self.with_nonce(public_key, async |nonce| {
341            let client = self.client.mutate::<ContextConfig>(
342                self.client.config.protocol.as_ref().into(),
343                self.client.config.network_id.as_ref().into(),
344                self.client.config.contract_id.as_ref().into(),
345            );
346
347            client
348                .grant(
349                    self.client.context_id.rt().expect("infallible conversion"),
350                    &capabilities,
351                )
352                .send(**private_key, nonce)
353                .await
354        })
355        .await?;
356
357        Ok(())
358    }
359
360    pub async fn revoke(
361        &mut self,
362        public_key: &PublicKey,
363        capabilities: &[(PublicKey, Capability)],
364    ) -> eyre::Result<()> {
365        let identity = self
366            .client
367            .context_client()
368            .get_identity(&self.client.context_id, public_key)?
369            .ok_or_eyre("identity not found")?;
370
371        let private_key = identity.private_key()?;
372
373        let capabilities = capabilities
374            .iter()
375            .map(|(who, cap)| who.rt().map(|who| (who, *cap)))
376            .collect::<Result<Vec<_>, _>>()
377            .expect("infallible conversion");
378
379        self.with_nonce(public_key, async |nonce| {
380            let client = self.client.mutate::<ContextConfig>(
381                self.client.config.protocol.as_ref().into(),
382                self.client.config.network_id.as_ref().into(),
383                self.client.config.contract_id.as_ref().into(),
384            );
385
386            client
387                .revoke(
388                    self.client.context_id.rt().expect("infallible conversion"),
389                    &capabilities,
390                )
391                .send(**private_key, nonce)
392                .await
393        })
394        .await?;
395
396        Ok(())
397    }
398
399    pub async fn update_proxy_contract(&mut self, public_key: &PublicKey) -> eyre::Result<()> {
400        let identity = self
401            .client
402            .context_client()
403            .get_identity(&self.client.context_id, public_key)?
404            .ok_or_eyre("identity not found")?;
405
406        let private_key = identity.private_key()?;
407
408        self.with_nonce(public_key, async |nonce| {
409            let client = self.client.mutate::<ContextConfig>(
410                self.client.config.protocol.as_ref().into(),
411                self.client.config.network_id.as_ref().into(),
412                self.client.config.contract_id.as_ref().into(),
413            );
414
415            client
416                .update_proxy_contract(self.client.context_id.rt().expect("infallible conversion"))
417                .send(**private_key, nonce)
418                .await
419        })
420        .await?;
421
422        Ok(())
423    }
424
425    pub async fn application(&self) -> eyre::Result<Application> {
426        let client = self.client.query::<ContextConfig>(
427            self.client.config.protocol.as_ref().into(),
428            self.client.config.network_id.as_ref().into(),
429            self.client.config.contract_id.as_ref().into(),
430        );
431
432        let application = client
433            .application(self.client.context_id.rt().expect("infallible conversion"))
434            .await?;
435
436        let application = Application::new(
437            application.id.as_bytes().into(),
438            ApplicationBlob {
439                bytecode: application.blob.as_bytes().into(),
440                compiled: BlobId::from([0; 32]),
441            },
442            application.size,
443            application.source.0.parse()?,
444            application.metadata.0.into_inner().into_owned(),
445        );
446
447        Ok(application)
448    }
449
450    pub async fn application_revision(&self) -> eyre::Result<u64> {
451        let client = self.client.query::<ContextConfig>(
452            self.client.config.protocol.as_ref().into(),
453            self.client.config.network_id.as_ref().into(),
454            self.client.config.contract_id.as_ref().into(),
455        );
456
457        let revision = client
458            .application_revision(self.client.context_id.rt().expect("infallible conversion"))
459            .await?;
460
461        Ok(revision)
462    }
463
464    pub async fn members(&self, offset: usize, length: usize) -> eyre::Result<Vec<PublicKey>> {
465        let client = self.client.query::<ContextConfig>(
466            self.client.config.protocol.as_ref().into(),
467            self.client.config.network_id.as_ref().into(),
468            self.client.config.contract_id.as_ref().into(),
469        );
470
471        let members = client
472            .members(
473                self.client.context_id.rt().expect("infallible conversion"),
474                offset,
475                length,
476            )
477            .await?;
478
479        let members = members
480            .into_iter()
481            .map(|identity| identity.as_bytes().into())
482            .collect();
483
484        Ok(members)
485    }
486
487    pub async fn has_member(&self, identity: &PublicKey) -> eyre::Result<bool> {
488        let client = self.client.query::<ContextConfig>(
489            self.client.config.protocol.as_ref().into(),
490            self.client.config.network_id.as_ref().into(),
491            self.client.config.contract_id.as_ref().into(),
492        );
493
494        let has_member = client
495            .has_member(
496                self.client.context_id.rt().expect("infallible conversion"),
497                identity.rt().expect("infallible conversion"),
498            )
499            .await?;
500
501        Ok(has_member)
502    }
503
504    pub async fn members_revision(&self) -> eyre::Result<u64> {
505        let client = self.client.query::<ContextConfig>(
506            self.client.config.protocol.as_ref().into(),
507            self.client.config.network_id.as_ref().into(),
508            self.client.config.contract_id.as_ref().into(),
509        );
510
511        let revision = client
512            .members_revision(self.client.context_id.rt().expect("infallible conversion"))
513            .await?;
514
515        Ok(revision)
516    }
517
518    // todo! reintroduce when PublicKey: Ord
519    // pub async fn privileges(
520    //     &self,
521    //     identities: &[PublicKey],
522    // ) -> eyre::Result<BTreeMap<PublicKey, Vec<Capability>>> {
523    //     let client = self.client.query::<ContextConfig>(
524    //         self.client.config.protocol.as_ref().into(),
525    //         self.client.config.network_id.as_ref().into(),
526    //         self.client.config.contract_id.as_ref().into(),
527    //     );
528
529    //     let identities = identities
530    //         .iter()
531    //         .map(|e| e.rt())
532    //         .collect::<Result<Vec<_>, _>>()
533    //         .expect("infallible conversion");
534
535    //     let privileges = client
536    //         .privileges(
537    //             self.client.context_id.rt().expect("infallible conversion"),
538    //             &identities,
539    //         )
540    //         .await?;
541
542    //     let privileges = privileges
543    //         .into_iter()
544    //         .map(|(identity, capabilities)| (identity.as_bytes().into(), capabilities))
545    //         .collect();
546
547    //     Ok(privileges)
548    // }
549
550    pub async fn get_proxy_contract(&self) -> eyre::Result<String> {
551        let client = self.client.query::<ContextConfig>(
552            self.client.config.protocol.as_ref().into(),
553            self.client.config.network_id.as_ref().into(),
554            self.client.config.contract_id.as_ref().into(),
555        );
556
557        let proxy_contract = client
558            .get_proxy_contract(self.client.context_id.rt().expect("infallible conversion"))
559            .await?;
560
561        Ok(proxy_contract)
562    }
563}