calimero_node_primitives/client/
application.rs

1use std::io;
2use std::sync::Arc;
3
4use calimero_primitives::application::{
5    Application, ApplicationBlob, ApplicationId, ApplicationSource,
6};
7use calimero_primitives::blobs::BlobId;
8use calimero_primitives::hash::Hash;
9use calimero_store::{key, types};
10use camino::Utf8PathBuf;
11use eyre::bail;
12use futures_util::TryStreamExt;
13use reqwest::Url;
14use tokio::fs::File;
15use tokio_util::compat::TokioAsyncReadCompatExt;
16
17use super::NodeClient;
18
19impl NodeClient {
20    pub fn get_application(
21        &self,
22        application_id: &ApplicationId,
23    ) -> eyre::Result<Option<Application>> {
24        let handle = self.datastore.handle();
25
26        let key = key::ApplicationMeta::new(*application_id);
27
28        let Some(application) = handle.get(&key)? else {
29            return Ok(None);
30        };
31
32        let application = Application::new(
33            *application_id,
34            ApplicationBlob {
35                bytecode: application.bytecode.blob_id(),
36                compiled: application.compiled.blob_id(),
37            },
38            application.size,
39            application.source.parse()?,
40            application.metadata.into_vec(),
41        );
42
43        Ok(Some(application))
44    }
45
46    pub async fn get_application_bytes(
47        &self,
48        application_id: &ApplicationId,
49    ) -> eyre::Result<Option<Arc<[u8]>>> {
50        let handle = self.datastore.handle();
51
52        let key = key::ApplicationMeta::new(*application_id);
53
54        let Some(application) = handle.get(&key)? else {
55            return Ok(None);
56        };
57
58        let Some(bytes) = self
59            .get_blob_bytes(&application.bytecode.blob_id(), None)
60            .await?
61        else {
62            bail!("fatal: application points to dangling blob");
63        };
64
65        Ok(Some(bytes))
66    }
67
68    pub fn has_application(&self, application_id: &ApplicationId) -> eyre::Result<bool> {
69        let handle = self.datastore.handle();
70
71        let key = key::ApplicationMeta::new(*application_id);
72
73        if let Some(application) = handle.get(&key)? {
74            return self.has_blob(&application.bytecode.blob_id());
75        }
76
77        Ok(false)
78    }
79
80    pub fn install_application(
81        &self,
82        blob_id: &BlobId,
83        size: u64,
84        source: &ApplicationSource,
85        metadata: Vec<u8>,
86    ) -> eyre::Result<ApplicationId> {
87        let application = types::ApplicationMeta::new(
88            key::BlobMeta::new(*blob_id),
89            size,
90            source.to_string().into_boxed_str(),
91            metadata.into_boxed_slice(),
92            key::BlobMeta::new(BlobId::from([0; 32])),
93        );
94
95        let application_id = {
96            let components = (
97                application.bytecode,
98                application.size,
99                &application.source,
100                &application.metadata,
101            );
102
103            ApplicationId::from(*Hash::hash_borsh(&components)?)
104        };
105
106        let mut handle = self.datastore.handle();
107
108        let key = key::ApplicationMeta::new(application_id);
109
110        handle.put(&key, &application)?;
111
112        Ok(application_id)
113    }
114
115    pub async fn install_application_from_path(
116        &self,
117        path: Utf8PathBuf,
118        metadata: Vec<u8>,
119    ) -> eyre::Result<ApplicationId> {
120        let path = path.canonicalize_utf8()?;
121
122        let file = File::open(&path).await?;
123
124        let expected_size = file.metadata().await?.len();
125
126        let (blob_id, size) = self
127            .add_blob(file.compat(), Some(expected_size), None)
128            .await?;
129
130        let Ok(uri) = Url::from_file_path(path) else {
131            bail!("non-absolute path")
132        };
133
134        self.install_application(&blob_id, size, &uri.as_str().parse()?, metadata)
135    }
136
137    pub async fn install_application_from_url(
138        &self,
139        url: Url,
140        metadata: Vec<u8>,
141        expected_hash: Option<&Hash>,
142    ) -> eyre::Result<ApplicationId> {
143        let uri = url.as_str().parse()?;
144
145        let response = reqwest::Client::new().get(url).send().await?;
146
147        let expected_size = response.content_length();
148
149        let (blob_id, size) = self
150            .add_blob(
151                response
152                    .bytes_stream()
153                    .map_err(io::Error::other)
154                    .into_async_read(),
155                expected_size,
156                expected_hash,
157            )
158            .await?;
159
160        self.install_application(&blob_id, size, &uri, metadata)
161    }
162
163    pub fn uninstall_application(&self, application_id: &ApplicationId) -> eyre::Result<()> {
164        let mut handle = self.datastore.handle();
165
166        let key = key::ApplicationMeta::new(*application_id);
167
168        handle.delete(&key)?;
169
170        Ok(())
171    }
172
173    pub fn list_applications(&self) -> eyre::Result<Vec<Application>> {
174        let handle = self.datastore.handle();
175
176        let mut iter = handle.iter::<key::ApplicationMeta>()?;
177
178        let mut applications = vec![];
179
180        for (id, app) in iter.entries() {
181            let (id, app) = (id?, app?);
182            applications.push(Application::new(
183                id.application_id(),
184                ApplicationBlob {
185                    bytecode: app.bytecode.blob_id(),
186                    compiled: app.compiled.blob_id(),
187                },
188                app.size,
189                app.source.parse()?,
190                app.metadata.to_vec(),
191            ));
192        }
193
194        Ok(applications)
195    }
196
197    pub fn update_compiled_app(
198        &self,
199        application_id: &ApplicationId,
200        compiled_blob_id: &BlobId,
201    ) -> eyre::Result<()> {
202        let mut handle = self.datastore.handle();
203
204        let key = key::ApplicationMeta::new(*application_id);
205
206        let Some(mut application) = handle.get(&key)? else {
207            bail!("application not found");
208        };
209
210        application.compiled = key::BlobMeta::new(*compiled_blob_id);
211
212        handle.put(&key, &application)?;
213
214        Ok(())
215    }
216}