autonomi 0.9.0

Autonomi client API
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
// Copyright 2024 MaidSafe.net limited.
//
// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

pub mod key;
pub mod user_data;

pub use key::{VaultSecretKey, vault_derive_key};
pub use user_data::UserData;

use crate::client::config::FILE_UPLOAD_BATCH_SIZE;
use crate::client::data_types::scratchpad::ScratchpadError;
use crate::client::key_derivation::{DerivationIndex, MainSecretKey};
use crate::client::payment::PaymentOption;
use crate::client::quote::CostError;
use crate::client::{Client, GetError};
use crate::graph::GraphError;
use crate::utils::process_tasks_with_max_concurrency;
use ant_evm::{AttoTokens, U256};
use ant_protocol::Bytes;
use ant_protocol::storage::{
    GraphContent, GraphEntry, GraphEntryAddress, Scratchpad, ScratchpadAddress,
};
use bls::PublicKey;
use std::hash::{DefaultHasher, Hash, Hasher};
use tracing::info;

/// The content type of the vault data
/// The number is used to determine the type of the contents of the bytes contained in a vault
/// Custom apps can use this to store their own custom types of data in vaults
/// It is recommended to use the hash of the app name or an unique identifier as the content type using [`vault_content_type_from_app_name`]
/// The value 0 is reserved for tests
pub type VaultContentType = u64;

/// Defines the max size of content can be written into per ScratchPad
pub const MAX_CONTENT_PER_SCRATCHPAD: usize = Scratchpad::MAX_SIZE - 1024;

/// Defines the max number of Scratchpads that one GraphEntry can point to
/// The current value is assuming GraphEntry max_size to be 100KB.
pub const NUM_OF_SCRATCHPADS_PER_GRAPH_ENTRY: usize = 1_000;

/// Hard coded derivation index for the Vault's root GraphEntry.
/// Derive the Vault's main secret/public key by it to get the root GraphEntry owner/address
pub const VAULT_HEAD_DERIVATION_INDEX: [u8; 32] = [0; 32];

/// For custom apps using Vault, this function converts an app identifier or name to a [`VaultContentType`]
pub fn vault_content_type_from_app_name<T: Hash>(s: T) -> VaultContentType {
    let mut hasher = DefaultHasher::new();
    s.hash(&mut hasher);
    hasher.finish()
}

/// @deprecated Use [`vault_content_type_from_app_name`] instead. This function will be removed in a future version.
#[deprecated(
    since = "0.6.0",
    note = "Use `vault_content_type_from_app_name` instead"
)]
pub fn app_name_to_vault_content_type<T: Hash>(s: T) -> VaultContentType {
    vault_content_type_from_app_name(s)
}

#[derive(Debug, thiserror::Error)]
pub enum VaultError {
    #[error("Vault Scratchpad related error: {0}")]
    Scratchpad(#[from] ScratchpadError),
    #[error("Vault GraphEntry related error: {0}")]
    GraphEntry(#[from] GraphError),
    #[error("Vault Cost related error: {0}")]
    Cost(#[from] CostError),
    #[error("Protocol: {0}")]
    Protocol(#[from] ant_protocol::Error),
    #[error("Vault doesn't have enough graph descendants: {0}")]
    VaultNotEnoughGraphDescendants(String),
    #[error("Vault with empty content")]
    VaultWithZeroContentSize,
}

impl Client {
    /// Retrieves and returns a decrypted vault if one exists.
    ///
    /// Returns the content type of the bytes in the vault.
    pub async fn vault_get(
        &self,
        secret_key: &VaultSecretKey,
    ) -> Result<(Bytes, VaultContentType), VaultError> {
        info!("Fetching and decrypting vault...");
        let main_secret_key = MainSecretKey::new(secret_key.clone());
        let public_key = main_secret_key
            .derive_key(&DerivationIndex::from_bytes(VAULT_HEAD_DERIVATION_INDEX))
            .public_key();

        let mut cur_graph_entry_addr = GraphEntryAddress::new(public_key.into());
        let mut decrypted_full_text = vec![];
        let mut content_type = 0;
        let mut has_end_reached = false;

        while !has_end_reached {
            let graph_entry = self.graph_entry_get(&cur_graph_entry_addr).await?;

            // The first descendant is reserved for `expand GraphEntry`.
            match graph_entry.descendants.split_first() {
                Some((&(first, _), rest)) => {
                    cur_graph_entry_addr = GraphEntryAddress::new(first);
                    let scratchpad_addresses = rest.to_vec();

                    let (decrypt_data, cur_content_type, is_end_reached) = self
                        .fetch_scratchpads_of_one_graph_entry_and_decrypt(
                            &main_secret_key,
                            scratchpad_addresses,
                        )
                        .await?;
                    decrypted_full_text.push(decrypt_data);
                    content_type = cur_content_type;
                    has_end_reached = is_end_reached;
                }
                None => {
                    let msg = format!(
                        "Vault's GraphEntry at {cur_graph_entry_addr:?} only has {} descendants.",
                        graph_entry.descendants.len()
                    );
                    return Err(VaultError::VaultNotEnoughGraphDescendants(msg));
                }
            }
        }

        debug!("vault data is successfully fetched and decrypted");
        Ok((Bytes::from(decrypted_full_text.concat()), content_type))
    }

    /// Get the cost of creating a new vault
    /// A quick estimation of cost:
    ///   num_of_graph_entry * graph_entry_cost + num_of_scratchpad * scratchpad_cost
    pub async fn vault_cost(
        &self,
        owner: &VaultSecretKey,
        max_size: u64,
    ) -> Result<AttoTokens, VaultError> {
        if max_size == 0 {
            return Err(VaultError::VaultWithZeroContentSize);
        }

        info!("Getting cost for vault");
        let public_key = MainSecretKey::new(owner.clone())
            .derive_key(&DerivationIndex::from_bytes(VAULT_HEAD_DERIVATION_INDEX))
            .public_key();
        let graph_entry_cost = self.graph_entry_cost(&public_key.into()).await?;
        if graph_entry_cost.is_zero() {
            // Has been created, assuming all Scratchpads have been created and paid
            Ok(graph_entry_cost)
        } else {
            let scratchpad_cost = self.scratchpad_cost(&public_key.into()).await?;

            let num_of_scratchpads = max_size / MAX_CONTENT_PER_SCRATCHPAD as u64 + 1;
            let num_of_graph_entry =
                num_of_scratchpads / NUM_OF_SCRATCHPADS_PER_GRAPH_ENTRY as u64 + 1;

            let total_cost = U256::from(num_of_graph_entry) * graph_entry_cost.as_atto()
                + U256::from(num_of_scratchpads) * scratchpad_cost.as_atto();
            Ok(AttoTokens::from_atto(total_cost))
        }
    }

    /// Put data into the client's VaultPacket
    ///
    /// Dynamically expand the vault capacity by paying for more space (Scratchpad) when needed.
    ///
    /// It is recommended to use the hash of the app name or unique identifier as the content type.
    pub async fn vault_put(
        &self,
        data: Bytes,
        payment_option: PaymentOption,
        secret_key: &VaultSecretKey,
        content_type: VaultContentType,
    ) -> Result<AttoTokens, VaultError> {
        if data.is_empty() {
            return Err(VaultError::VaultWithZeroContentSize);
        }

        info!("Writing {} bytes to vault ...", data.len());
        let mut total_cost = AttoTokens::zero();
        let main_secret_key = MainSecretKey::new(secret_key.clone());

        // scratchpad_derivations ordered by the collection order
        let (mut cur_free_graphentry_derivation, mut scratchpad_derivations) = self
            .vault_claimed_capacity(
                &main_secret_key,
                DerivationIndex::from_bytes(VAULT_HEAD_DERIVATION_INDEX),
            )
            .await?;

        let contents = vault_split_bytes(data);

        info!(
            "Current capacity is {}, meanwhile requiring {}",
            scratchpad_derivations.len(),
            contents.len()
        );

        // claim more capacity if short of.
        // Note: as the Scratchpad is `created on use`, hence during the `claim stage`,
        //       NUM_OF_SCRATCHPADS_PER_GRAPHENTRY to be claimed in one newly created GraphEntry.
        while scratchpad_derivations.len() < contents.len() {
            let (new_free_graphentry_derivation, new_scratchpad_derivations, graph_cost) = self
                .vault_expand_capacity(
                    &main_secret_key,
                    &cur_free_graphentry_derivation,
                    payment_option.clone(),
                )
                .await?;
            cur_free_graphentry_derivation = new_free_graphentry_derivation;
            scratchpad_derivations.extend(&new_scratchpad_derivations);
            total_cost = AttoTokens::from_atto(total_cost.as_atto() + graph_cost.as_atto());
        }

        // Convert to Vec of futures
        let update_futures: Vec<_> = contents
            .into_iter()
            .enumerate()
            .map(|(i, content)| {
                let sp_secret_key = main_secret_key
                    .derive_key(&DerivationIndex::from_bytes(scratchpad_derivations[i].1));
                let client = self.clone();
                let payment_option_clone = payment_option.clone();

                async move {
                    let target_addr = ScratchpadAddress::new(sp_secret_key.public_key().into());
                    let already_exists = self.scratchpad_check_existence(&target_addr).await?;

                    if already_exists {
                        info!(
                            "Updating Scratchpad at {target_addr:?} with content of {} bytes",
                            content.len()
                        );
                        match client
                            .scratchpad_update(&sp_secret_key.clone().into(), content_type, &content)
                            .await
                        {
                            Ok(()) => {
                                info!(
                                    "Updated Scratchpad at {target_addr:?} with content of {} bytes",
                                    content.len()
                                );
                                Ok(None)
                            }
                            Err(err) => Err(err.into()),
                        }
                    } else {
                        info!("Creating Scratchpad at {target_addr:?}");
                        let (price, addr) = client
                            .scratchpad_create(
                                &sp_secret_key.into(),
                                content_type,
                                &content,
                                payment_option_clone,
                            )
                            .await?;
                        info!("Created Scratchpad at {addr:?} with cost of {price:?}");
                        Ok(Some(price))
                    }
                }
            })
            .collect();

        let update_results =
            process_tasks_with_max_concurrency(update_futures, *FILE_UPLOAD_BATCH_SIZE).await;

        // Process results
        for result in update_results {
            match result {
                Ok(Some(price)) => {
                    total_cost = AttoTokens::from_atto(total_cost.as_atto() + price.as_atto());
                }
                Ok(None) => (),
                Err(e) => return Err(e),
            }
        }

        Ok(total_cost)
    }

    // Expand the capacity, i.e. upload one GraphEntry
    // The returned value is:
    //   * cur_free_graphentry_derivation: the output[0] of the tail of the linked GraphEntry
    //   * scratchpad_derivations: ordered by the creating order
    //   * graph_cost: cost paid to upload the GraphEntry
    pub async fn vault_expand_capacity(
        &self,
        main_secret_key: &MainSecretKey,
        cur_graphentry_derivation: &DerivationIndex,
        payment_option: PaymentOption,
    ) -> Result<(DerivationIndex, Vec<(PublicKey, GraphContent)>, AttoTokens), VaultError> {
        let own_secret_key = main_secret_key.derive_key(cur_graphentry_derivation);

        // For Vault, doesn't need the backward poining. i.e. one-direction link shall be enough.
        let parents = vec![];
        // For Vault, doesn't need this field to be populated.
        let initial_value = [0u8; 32];

        // Poining to the next GraphEntry
        let new_graphentry_derivation = DerivationIndex::random(&mut rand::thread_rng());
        let public_key: PublicKey = main_secret_key
            .derive_key(&new_graphentry_derivation)
            .public_key()
            .into();
        let mut descendants = vec![(public_key, new_graphentry_derivation.into_bytes())];

        // Pointing to other future Scrachpads
        descendants.extend((0..NUM_OF_SCRATCHPADS_PER_GRAPH_ENTRY).map(|_| {
            let derivation_index = DerivationIndex::random(&mut rand::thread_rng());
            let public_key: PublicKey = main_secret_key
                .derive_key(&derivation_index)
                .public_key()
                .into();
            (public_key, derivation_index.into_bytes())
        }));

        let graph_entry = GraphEntry::new(
            &own_secret_key.into(),
            parents,
            initial_value,
            descendants.clone(),
        );

        // Upload the GraphEntry
        let (graph_cost, _addr) = self.graph_entry_put(graph_entry, payment_option).await?;

        let scratchpad_derivations = descendants.split_off(1);
        Ok((
            new_graphentry_derivation,
            scratchpad_derivations,
            graph_cost,
        ))
    }

    // Collects the current claimed capacity (i.e. the uploaded `GrapthEntry`s)
    // The returned value is:
    //   * cur_free_graphentry_derivation: i.e. the root if no graph_entry uploaded,
    //       otherwise, the first un-used one (the output[0] of the tail of the linked GraphEntry)
    //   * scratchpad_derivations: ordered by the collection order
    pub async fn vault_claimed_capacity(
        &self,
        main_secret_key: &MainSecretKey,
        mut cur_free_graphentry_derivation: DerivationIndex,
    ) -> Result<(DerivationIndex, Vec<(PublicKey, GraphContent)>), VaultError> {
        let mut scratchpad_derivations = vec![];
        loop {
            let public_key = main_secret_key
                .derive_key(&cur_free_graphentry_derivation)
                .public_key();
            let cur_graph_entry_addr = GraphEntryAddress::new(public_key.into());

            match self.graph_entry_get(&cur_graph_entry_addr).await {
                Ok(entry) => {
                    // A GraphEntry was created with all NUM_OF_SCRATCHPADS_PER_GRAPHENTRY
                    // scratchpad claimed:
                    //   * the first descendant pointing to next GraphEntry.
                    //   * other descendants pointing to Scratchpads for content.
                    if entry.descendants.len() <= NUM_OF_SCRATCHPADS_PER_GRAPH_ENTRY {
                        let msg = format!(
                            "Vault's GraphEntry at {cur_graph_entry_addr:?} only has {} descendants.",
                            entry.descendants.len()
                        );
                        return Err(VaultError::VaultNotEnoughGraphDescendants(msg));
                    }
                    cur_free_graphentry_derivation =
                        DerivationIndex::from_bytes(entry.descendants[0].1);
                    scratchpad_derivations.extend(&entry.descendants[1..]);
                }
                Err(GraphError::GetError(GetError::RecordNotFound)) => {
                    // GraphEntry not existed, return the current snapshot.
                    info!(
                        "vault capacity is successfully fetched, with {} scratchpads",
                        scratchpad_derivations.len()
                    );
                    return Ok((cur_free_graphentry_derivation, scratchpad_derivations));
                }
                Err(err) => {
                    return Err(err.into());
                }
            }
        }
    }

    async fn fetch_scratchpads_of_one_graph_entry_and_decrypt(
        &self,
        main_secret_key: &MainSecretKey,
        scratchpad_addresses: Vec<(PublicKey, [u8; 32])>,
    ) -> Result<(Bytes, VaultContentType, bool), VaultError> {
        let mut decrypted_full_text = vec![];
        let mut content_type = 0;
        let mut has_end_reached = false;
        // Any non-max-sized ScratchPad indicates the end-of-vault-content.
        for (pub_key, derive_bytes) in scratchpad_addresses {
            let addr = ScratchpadAddress::new(pub_key);
            let secret_key = main_secret_key.derive_key(&DerivationIndex::from_bytes(derive_bytes));

            let sp = self.scratchpad_get(&addr).await?;
            content_type = sp.data_encoding();
            let decrypt_data = sp.decrypt_data(&secret_key.into())?;
            decrypted_full_text.push(decrypt_data);
            if sp.encrypted_data().len() < MAX_CONTENT_PER_SCRATCHPAD {
                has_end_reached = true;
                break;
            }
        }

        Ok((
            Bytes::from(decrypted_full_text.concat()),
            content_type,
            has_end_reached,
        ))
    }

    /// @deprecated Use `vault_get` instead. This function will be removed in a future version.
    #[deprecated(since = "0.2.0", note = "Use `vault_get` instead")]
    pub async fn fetch_and_decrypt_vault(
        &self,
        secret_key: &VaultSecretKey,
    ) -> Result<(Bytes, VaultContentType), VaultError> {
        self.vault_get(secret_key).await
    }

    /// @deprecated Use `vault_put` instead. This function will be removed in a future version.
    #[deprecated(since = "0.2.0", note = "Use `vault_put` instead")]
    pub async fn write_bytes_to_vault(
        &self,
        data: Bytes,
        payment_option: PaymentOption,
        secret_key: &VaultSecretKey,
        content_type: VaultContentType,
    ) -> Result<AttoTokens, VaultError> {
        self.vault_put(data, payment_option, secret_key, content_type)
            .await
    }
}

pub fn vault_split_bytes(input: Bytes) -> Vec<Bytes> {
    let mut contents = Vec::new();
    let mut offset = 0;

    while offset < input.len() {
        let end = (offset + MAX_CONTENT_PER_SCRATCHPAD).min(input.len());
        contents.push(input.slice(offset..end));
        offset = end;
    }

    contents
}