diff --git a/autonomi-cli/src/commands/vault.rs b/autonomi-cli/src/commands/vault.rs index e7ce3f95c8..b5446f8962 100644 --- a/autonomi-cli/src/commands/vault.rs +++ b/autonomi-cli/src/commands/vault.rs @@ -40,7 +40,7 @@ pub async fn create(peers: Vec) -> Result<()> { println!("Pushing to network vault..."); let total_cost = client - .put_user_data_to_vault(&vault_sk, &wallet, local_user_data) + .put_user_data_to_vault(&vault_sk, wallet.into(), local_user_data) .await?; if total_cost.is_zero() { @@ -82,7 +82,7 @@ pub async fn sync(peers: Vec, force: bool) -> Result<()> { let private_file_archives_len = local_user_data.private_file_archives.len(); let registers_len = local_user_data.registers.len(); client - .put_user_data_to_vault(&vault_sk, &wallet, local_user_data) + .put_user_data_to_vault(&vault_sk, wallet.into(), local_user_data) .await?; println!("✅ Successfully synced vault"); diff --git a/autonomi/src/client/archive.rs b/autonomi/src/client/archive.rs index 04ad120b19..9d5f1de78a 100644 --- a/autonomi/src/client/archive.rs +++ b/autonomi/src/client/archive.rs @@ -168,7 +168,7 @@ impl Client { let bytes = archive .into_bytes() .map_err(|e| PutError::Serialization(format!("Failed to serialize archive: {e:?}")))?; - self.data_put(bytes, wallet).await + self.data_put(bytes, wallet.into()).await } /// Get the cost to upload an archive diff --git a/autonomi/src/client/archive_private.rs b/autonomi/src/client/archive_private.rs index a7ba854380..7354634140 100644 --- a/autonomi/src/client/archive_private.rs +++ b/autonomi/src/client/archive_private.rs @@ -19,9 +19,9 @@ use super::{ data_private::PrivateDataAccess, Client, }; +use crate::client::payment::PaymentOption; use bytes::Bytes; use serde::{Deserialize, Serialize}; -use sn_evm::EvmWallet; /// The address of a private archive /// Contains the [`PrivateDataAccess`] leading to the [`PrivateArchive`] data @@ -130,11 +130,11 @@ impl Client { pub async fn private_archive_put( &self, archive: PrivateArchive, - wallet: &EvmWallet, + payment_option: PaymentOption, ) -> Result { let bytes = archive .into_bytes() .map_err(|e| PutError::Serialization(format!("Failed to serialize archive: {e:?}")))?; - self.private_data_put(bytes, wallet).await + self.private_data_put(bytes, payment_option).await } } diff --git a/autonomi/src/client/data.rs b/autonomi/src/client/data.rs index 86f500bc57..ec7ebf6d70 100644 --- a/autonomi/src/client/data.rs +++ b/autonomi/src/client/data.rs @@ -13,11 +13,12 @@ use std::collections::HashSet; use std::sync::LazyLock; use xor_name::XorName; +use crate::client::payment::PaymentOption; use crate::client::utils::process_tasks_with_max_concurrency; use crate::client::{ClientEvent, UploadSummary}; use crate::{self_encryption::encrypt, Client}; +use sn_evm::EvmWalletError; use sn_evm::{Amount, AttoTokens}; -use sn_evm::{EvmWallet, EvmWalletError}; use sn_networking::{GetRecordCfg, NetworkError}; use sn_protocol::{ storage::{try_deserialize_record, Chunk, ChunkAddress, RecordHeader, RecordKind}, @@ -136,7 +137,11 @@ impl Client { /// Upload a piece of data to the network. /// Returns the Data Address at which the data was stored. /// This data is publicly accessible. - pub async fn data_put(&self, data: Bytes, wallet: &EvmWallet) -> Result { + pub async fn data_put( + &self, + data: Bytes, + payment_option: PaymentOption, + ) -> Result { let now = sn_networking::target_arch::Instant::now(); let (data_map_chunk, chunks) = encrypt(data)?; let data_map_addr = data_map_chunk.address(); @@ -152,8 +157,8 @@ impl Client { // Pay for all chunks + data map chunk info!("Paying for {} addresses", xor_names.len()); - let (payment_proofs, _free_chunks) = self - .pay(xor_names.into_iter(), wallet) + let receipt = self + .pay_for_content_addrs(xor_names.into_iter(), payment_option) .await .inspect_err(|err| error!("Error paying for data: {err:?}"))?; @@ -163,7 +168,7 @@ impl Client { for chunk in chunks.into_iter().chain(std::iter::once(data_map_chunk)) { let self_clone = self.clone(); let address = *chunk.address(); - if let Some(proof) = payment_proofs.get(chunk.name()) { + if let Some(proof) = receipt.get(chunk.name()) { let proof_clone = proof.clone(); upload_tasks.push(async move { self_clone @@ -191,7 +196,7 @@ impl Client { // Reporting if let Some(channel) = self.client_event_sender.as_ref() { - let tokens_spent = payment_proofs + let tokens_spent = receipt .values() .map(|proof| proof.quote.cost.as_atto()) .sum::(); diff --git a/autonomi/src/client/data_private.rs b/autonomi/src/client/data_private.rs index d2ecaf0a2b..29925b915b 100644 --- a/autonomi/src/client/data_private.rs +++ b/autonomi/src/client/data_private.rs @@ -10,11 +10,12 @@ use std::hash::{DefaultHasher, Hash, Hasher}; use bytes::Bytes; use serde::{Deserialize, Serialize}; -use sn_evm::{Amount, EvmWallet}; +use sn_evm::Amount; use sn_protocol::storage::Chunk; use super::data::CHUNK_UPLOAD_BATCH_SIZE; use super::data::{GetError, PutError}; +use crate::client::payment::PaymentOption; use crate::client::utils::process_tasks_with_max_concurrency; use crate::client::{ClientEvent, UploadSummary}; use crate::{self_encryption::encrypt, Client}; @@ -64,7 +65,7 @@ impl Client { pub async fn private_data_put( &self, data: Bytes, - wallet: &EvmWallet, + payment_option: PaymentOption, ) -> Result { let now = sn_networking::target_arch::Instant::now(); let (data_map_chunk, chunks) = encrypt(data)?; @@ -73,8 +74,8 @@ impl Client { // Pay for all chunks let xor_names: Vec<_> = chunks.iter().map(|chunk| *chunk.name()).collect(); info!("Paying for {} addresses", xor_names.len()); - let (payment_proofs, _free_chunks) = self - .pay(xor_names.into_iter(), wallet) + let receipt = self + .pay_for_content_addrs(xor_names.into_iter(), payment_option) .await .inspect_err(|err| error!("Error paying for data: {err:?}"))?; @@ -84,7 +85,7 @@ impl Client { for chunk in chunks { let self_clone = self.clone(); let address = *chunk.address(); - if let Some(proof) = payment_proofs.get(chunk.name()) { + if let Some(proof) = receipt.get(chunk.name()) { let proof_clone = proof.clone(); upload_tasks.push(async move { self_clone @@ -112,7 +113,7 @@ impl Client { // Reporting if let Some(channel) = self.client_event_sender.as_ref() { - let tokens_spent = payment_proofs + let tokens_spent = receipt .values() .map(|proof| proof.quote.cost.as_atto()) .sum::(); diff --git a/autonomi/src/client/external_signer.rs b/autonomi/src/client/external_signer.rs index b17002bd9c..c4f473d378 100644 --- a/autonomi/src/client/external_signer.rs +++ b/autonomi/src/client/external_signer.rs @@ -1,36 +1,23 @@ -use crate::client::data::{DataAddr, PutError}; +use crate::client::data::PutError; use crate::client::utils::extract_quote_payments; use crate::self_encryption::encrypt; use crate::Client; use bytes::Bytes; -use sn_evm::{PaymentQuote, ProofOfPayment, QuotePayment}; +use sn_evm::{PaymentQuote, QuotePayment}; use sn_protocol::storage::Chunk; use std::collections::HashMap; use xor_name::XorName; +use crate::utils::cost_map_to_quotes; #[allow(unused_imports)] pub use sn_evm::external_signer::*; impl Client { - /// Upload a piece of data to the network. This data will be self-encrypted. - /// Payment will not be done automatically as opposed to the regular `data_put`, so the proof of payment has to be provided. - /// Returns the Data Address at which the data was stored. - pub async fn data_put_with_proof_of_payment( - &self, - data: Bytes, - proof: HashMap, - ) -> Result { - let (data_map_chunk, chunks, _) = encrypt_data(data)?; - self.upload_data_map(&proof, &data_map_chunk).await?; - self.upload_chunks(&chunks, &proof).await?; - Ok(*data_map_chunk.address().xorname()) - } - /// Get quotes for data. /// Returns a cost map, data payments to be executed and a list of free (already paid for) chunks. - pub async fn get_quotes_for_data( + pub async fn get_quotes_for_content_addresses( &self, - data: Bytes, + content_addrs: impl Iterator, ) -> Result< ( HashMap, @@ -39,59 +26,18 @@ impl Client { ), PutError, > { - // Encrypt the data as chunks - let (_data_map_chunk, _chunks, xor_names) = encrypt_data(data)?; - - let cost_map: HashMap = self - .get_store_quotes(xor_names.into_iter()) - .await? - .into_iter() - .map(|(name, (_, _, q))| (name, q)) - .collect(); - + let cost_map = self.get_store_quotes(content_addrs).await?; let (quote_payments, free_chunks) = extract_quote_payments(&cost_map); - Ok((cost_map, quote_payments, free_chunks)) - } + let quotes = cost_map_to_quotes(cost_map); - async fn upload_data_map( - &self, - payment_proofs: &HashMap, - data_map_chunk: &Chunk, - ) -> Result<(), PutError> { - let map_xor_name = data_map_chunk.name(); - - if let Some(proof) = payment_proofs.get(map_xor_name) { - debug!("Uploading data map chunk: {map_xor_name:?}"); - self.chunk_upload_with_payment(data_map_chunk.clone(), proof.clone()) - .await - .inspect_err(|err| error!("Error uploading data map chunk: {err:?}")) - } else { - Ok(()) - } - } - - async fn upload_chunks( - &self, - chunks: &[Chunk], - payment_proofs: &HashMap, - ) -> Result<(), PutError> { - debug!("Uploading {} chunks", chunks.len()); - for chunk in chunks { - if let Some(proof) = payment_proofs.get(chunk.name()) { - let address = *chunk.address(); - self.chunk_upload_with_payment(chunk.clone(), proof.clone()) - .await - .inspect_err(|err| error!("Error uploading chunk {address:?} :{err:?}"))?; - } - } - Ok(()) + Ok((quotes, quote_payments, free_chunks)) } } /// Encrypts data as chunks. /// /// Returns the data map chunk, file chunks and a list of all content addresses including the data map. -fn encrypt_data(data: Bytes) -> Result<(Chunk, Vec, Vec), PutError> { +pub fn encrypt_data(data: Bytes) -> Result<(Chunk, Vec, Vec), PutError> { let now = sn_networking::target_arch::Instant::now(); let result = encrypt(data)?; diff --git a/autonomi/src/client/fs.rs b/autonomi/src/client/fs.rs index c1505224bc..40a43b9fba 100644 --- a/autonomi/src/client/fs.rs +++ b/autonomi/src/client/fs.rs @@ -154,7 +154,7 @@ impl Client { // upload archive let archive_serialized = archive.into_bytes()?; - let arch_addr = self.data_put(archive_serialized, wallet).await?; + let arch_addr = self.data_put(archive_serialized, wallet.into()).await?; info!("Complete archive upload completed in {:?}", start.elapsed()); #[cfg(feature = "loud")] @@ -175,7 +175,7 @@ impl Client { let data = tokio::fs::read(path).await?; let data = Bytes::from(data); - let addr = self.data_put(data, wallet).await?; + let addr = self.data_put(data, wallet.into()).await?; Ok(addr) } diff --git a/autonomi/src/client/fs_private.rs b/autonomi/src/client/fs_private.rs index 08d453ae37..d158916373 100644 --- a/autonomi/src/client/fs_private.rs +++ b/autonomi/src/client/fs_private.rs @@ -102,7 +102,9 @@ impl Client { // upload archive let archive_serialized = archive.into_bytes()?; - let arch_addr = self.private_data_put(archive_serialized, wallet).await?; + let arch_addr = self + .private_data_put(archive_serialized, wallet.into()) + .await?; info!( "Complete private archive upload completed in {:?}", @@ -126,7 +128,7 @@ impl Client { let data = tokio::fs::read(path).await?; let data = Bytes::from(data); - let addr = self.private_data_put(data, wallet).await?; + let addr = self.private_data_put(data, wallet.into()).await?; Ok(addr) } } diff --git a/autonomi/src/client/mod.rs b/autonomi/src/client/mod.rs index 0938dcbf9d..c4a2919347 100644 --- a/autonomi/src/client/mod.rs +++ b/autonomi/src/client/mod.rs @@ -7,6 +7,7 @@ // permissions and limitations relating to use of the SAFE Network Software. pub mod address; +pub mod payment; #[cfg(feature = "data")] pub mod archive; diff --git a/autonomi/src/client/payment.rs b/autonomi/src/client/payment.rs new file mode 100644 index 0000000000..fbff226294 --- /dev/null +++ b/autonomi/src/client/payment.rs @@ -0,0 +1,49 @@ +use crate::client::data::PayError; +use crate::Client; +use sn_evm::{EvmWallet, ProofOfPayment}; +use std::collections::HashMap; +use xor_name::XorName; + +/// Contains the proof of payment for XOR addresses. +pub type Receipt = HashMap; + +/// Payment options for data payments. +#[derive(Clone)] +pub enum PaymentOption { + Wallet(EvmWallet), + Receipt(Receipt), +} + +impl From for PaymentOption { + fn from(value: EvmWallet) -> Self { + PaymentOption::Wallet(value) + } +} + +impl From<&EvmWallet> for PaymentOption { + fn from(value: &EvmWallet) -> Self { + PaymentOption::Wallet(value.clone()) + } +} + +impl From for PaymentOption { + fn from(value: Receipt) -> Self { + PaymentOption::Receipt(value) + } +} + +impl Client { + pub(crate) async fn pay_for_content_addrs( + &self, + content_addrs: impl Iterator, + payment_option: PaymentOption, + ) -> Result { + match payment_option { + PaymentOption::Wallet(wallet) => { + let (receipt, _) = self.pay(content_addrs, &wallet).await?; + Ok(receipt) + } + PaymentOption::Receipt(receipt) => Ok(receipt), + } + } +} diff --git a/autonomi/src/client/utils.rs b/autonomi/src/client/utils.rs index 53c7f88747..bc17f9e58f 100644 --- a/autonomi/src/client/utils.rs +++ b/autonomi/src/client/utils.rs @@ -6,12 +6,14 @@ // 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. +use crate::client::payment::Receipt; +use crate::utils::receipt_from_cost_map_and_payments; use bytes::Bytes; use futures::stream::{FuturesUnordered, StreamExt}; use libp2p::kad::{Quorum, Record}; use rand::{thread_rng, Rng}; use self_encryption::{decrypt_full_set, DataMap, EncryptedChunk}; -use sn_evm::{EvmWallet, PaymentQuote, ProofOfPayment, QuotePayment}; +use sn_evm::{EvmWallet, ProofOfPayment, QuotePayment}; use sn_networking::{ GetRecordCfg, Network, NetworkError, PayeeQuote, PutRecordCfg, VerificationKind, }; @@ -28,7 +30,6 @@ use super::{ Client, }; use crate::self_encryption::DataMapLevel; -use crate::utils::payment_proof_from_quotes_and_payments; impl Client { /// Fetch and decrypt all chunks in the data map. @@ -160,13 +161,8 @@ impl Client { &self, content_addrs: impl Iterator, wallet: &EvmWallet, - ) -> Result<(HashMap, Vec), PayError> { - let cost_map = self - .get_store_quotes(content_addrs) - .await? - .into_iter() - .map(|(name, (_, _, q))| (name, q)) - .collect(); + ) -> Result<(Receipt, Vec), PayError> { + let cost_map = self.get_store_quotes(content_addrs).await?; let (quote_payments, skipped_chunks) = extract_quote_payments(&cost_map); @@ -187,7 +183,7 @@ impl Client { drop(lock_guard); debug!("Unlocked wallet"); - let proofs = payment_proof_from_quotes_and_payments(&cost_map, &payments); + let proofs = receipt_from_cost_map_and_payments(cost_map, &payments); trace!( "Chunk payments of {} chunks completed. {} chunks were free / already paid for", @@ -254,12 +250,12 @@ async fn fetch_store_quote( /// Form to be executed payments and already executed payments from a cost map. pub(crate) fn extract_quote_payments( - cost_map: &HashMap, + cost_map: &HashMap, ) -> (Vec, Vec) { let mut to_be_paid = vec![]; let mut already_paid = vec![]; - for (chunk_address, quote) in cost_map.iter() { + for (chunk_address, (_, _, quote)) in cost_map.iter() { if quote.cost.is_zero() { already_paid.push(*chunk_address); } else { diff --git a/autonomi/src/client/vault.rs b/autonomi/src/client/vault.rs index 55103b0578..209d174842 100644 --- a/autonomi/src/client/vault.rs +++ b/autonomi/src/client/vault.rs @@ -11,12 +11,13 @@ pub mod user_data; pub use key::{derive_vault_key, VaultSecretKey}; pub use user_data::UserData; -use xor_name::XorName; +use super::data::CostError; use crate::client::data::PutError; +use crate::client::payment::PaymentOption; use crate::client::Client; use libp2p::kad::{Quorum, Record}; -use sn_evm::{Amount, AttoTokens, EvmWallet}; +use sn_evm::{Amount, AttoTokens}; use sn_networking::{GetRecordCfg, GetRecordError, NetworkError, PutRecordCfg, VerificationKind}; use sn_protocol::storage::{ try_serialize_record, RecordKind, RetryStrategy, Scratchpad, ScratchpadAddress, @@ -27,8 +28,6 @@ use std::collections::HashSet; use std::hash::{DefaultHasher, Hash, Hasher}; use tracing::info; -use super::data::CostError; - #[derive(Debug, thiserror::Error)] pub enum VaultError { #[error("Could not generate Vault secret key from entropy: {0:?}")] @@ -171,35 +170,15 @@ impl Client { pub async fn write_bytes_to_vault( &self, data: Bytes, - wallet: &EvmWallet, + payment_option: PaymentOption, secret_key: &VaultSecretKey, content_type: VaultContentType, ) -> Result { let mut total_cost = AttoTokens::zero(); - let client_pk = secret_key.public_key(); - - let pad_res = self.get_vault_from_network(secret_key).await; - let mut is_new = true; - - let mut scratch = if let Ok(existing_data) = pad_res { - info!("Scratchpad already exists, returning existing data"); - - info!( - "scratch already exists, is version {:?}", - existing_data.count() - ); - is_new = false; - - if existing_data.owner() != &client_pk { - return Err(PutError::VaultBadOwner); - } - - existing_data - } else { - trace!("new scratchpad creation"); - Scratchpad::new(client_pk, content_type) - }; + let (mut scratch, is_new) = self + .get_or_create_scratchpad(secret_key, content_type) + .await?; let _ = scratch.update_and_sign(data, secret_key); debug_assert!(scratch.is_valid(), "Must be valid after being signed. This is a bug, please report it by opening an issue on our github"); @@ -210,21 +189,18 @@ impl Client { info!("Writing to vault at {scratch_address:?}",); let record = if is_new { - let scratch_xor = [&scratch_address] - .iter() - .filter_map(|f| f.as_xorname()) - .collect::>(); - let (payment_proofs, _) = self - .pay(scratch_xor.iter().cloned(), wallet) + let receipt = self + .pay_for_content_addrs(scratch.to_xor_name_vec().into_iter(), payment_option) .await .inspect_err(|err| { error!("Failed to pay for new vault at addr: {scratch_address:?} : {err}"); })?; - let proof = match payment_proofs.values().next() { + let proof = match receipt.values().next() { Some(proof) => proof, None => return Err(PutError::PaymentUnexpectedlyInvalid(scratch_address)), }; + total_cost = proof.quote.cost; Record { @@ -280,4 +256,38 @@ impl Client { Ok(total_cost) } + + /// Returns an existing scratchpad or creates a new one if it does not exist. + pub async fn get_or_create_scratchpad( + &self, + secret_key: &VaultSecretKey, + content_type: VaultContentType, + ) -> Result<(Scratchpad, bool), PutError> { + let client_pk = secret_key.public_key(); + + let pad_res = self.get_vault_from_network(secret_key).await; + let mut is_new = true; + + let scratch = if let Ok(existing_data) = pad_res { + info!("Scratchpad already exists, returning existing data"); + + info!( + "scratch already exists, is version {:?}", + existing_data.count() + ); + + is_new = false; + + if existing_data.owner() != &client_pk { + return Err(PutError::VaultBadOwner); + } + + existing_data + } else { + trace!("new scratchpad creation"); + Scratchpad::new(client_pk, content_type) + }; + + Ok((scratch, is_new)) + } } diff --git a/autonomi/src/client/vault/user_data.rs b/autonomi/src/client/vault/user_data.rs index 1f91b547bb..a0f217bda8 100644 --- a/autonomi/src/client/vault/user_data.rs +++ b/autonomi/src/client/vault/user_data.rs @@ -12,13 +12,13 @@ use crate::client::archive::ArchiveAddr; use crate::client::archive_private::PrivateArchiveAccess; use crate::client::data::GetError; use crate::client::data::PutError; +use crate::client::payment::PaymentOption; use crate::client::registers::RegisterAddress; use crate::client::vault::VaultError; use crate::client::vault::{app_name_to_vault_content_type, VaultContentType, VaultSecretKey}; use crate::client::Client; use serde::{Deserialize, Serialize}; use sn_evm::AttoTokens; -use sn_evm::EvmWallet; use sn_protocol::Bytes; use std::sync::LazyLock; @@ -75,11 +75,30 @@ impl UserData { self.file_archives.insert(archive, name) } + /// Add a private archive. Returning `Option::Some` with the old name if the archive was already in the set. + pub fn add_private_file_archive(&mut self, archive: PrivateArchiveAccess) -> Option { + self.private_file_archives.insert(archive, "".into()) + } + + /// Add a private archive with a name. Returning `Option::Some` with the old name if the archive was already in the set. + pub fn add_private_file_archive_with_name( + &mut self, + archive: PrivateArchiveAccess, + name: String, + ) -> Option { + self.private_file_archives.insert(archive, name) + } + /// Remove an archive. Returning `Option::Some` with the old name if the archive was already in the set. pub fn remove_file_archive(&mut self, archive: ArchiveAddr) -> Option { self.file_archives.remove(&archive) } + /// Remove a private archive. Returning `Option::Some` with the old name if the archive was already in the set. + pub fn remove_private_file_archive(&mut self, archive: PrivateArchiveAccess) -> Option { + self.private_file_archives.remove(&archive) + } + /// To bytes pub fn to_bytes(&self) -> Result { let bytes = rmp_serde::to_vec(&self)?; @@ -121,7 +140,7 @@ impl Client { pub async fn put_user_data_to_vault( &self, secret_key: &VaultSecretKey, - wallet: &EvmWallet, + payment_option: PaymentOption, user_data: UserData, ) -> Result { let bytes = user_data @@ -130,7 +149,7 @@ impl Client { let total_cost = self .write_bytes_to_vault( bytes, - wallet, + payment_option, secret_key, *USER_DATA_VAULT_CONTENT_IDENTIFIER, ) diff --git a/autonomi/src/client/wasm.rs b/autonomi/src/client/wasm.rs index f67c16babf..4400252f33 100644 --- a/autonomi/src/client/wasm.rs +++ b/autonomi/src/client/wasm.rs @@ -1,10 +1,10 @@ -use libp2p::Multiaddr; -use wasm_bindgen::prelude::*; - use super::address::{addr_to_str, str_to_addr}; - #[cfg(feature = "vault")] use super::vault::UserData; +use crate::client::data_private::PrivateDataAccess; +use crate::client::payment::Receipt; +use libp2p::Multiaddr; +use wasm_bindgen::prelude::*; /// The `Client` object allows interaction with the network to store and retrieve data. /// @@ -81,11 +81,45 @@ impl JsClient { #[wasm_bindgen(js_name = dataPut)] pub async fn data_put(&self, data: Vec, wallet: &JsWallet) -> Result { let data = crate::Bytes::from(data); - let xorname = self.0.data_put(data, &wallet.0).await?; + let xorname = self.0.data_put(data, (&wallet.0).into()).await?; Ok(addr_to_str(xorname)) } + /// Upload private data to the network. + /// + /// Returns the `PrivateDataAccess` chunk of the data. + #[wasm_bindgen(js_name = privateDataPut)] + pub async fn private_data_put( + &self, + data: Vec, + wallet: &JsWallet, + ) -> Result { + let data = crate::Bytes::from(data); + let private_data_access = self.0.private_data_put(data, (&wallet.0).into()).await?; + let js_value = serde_wasm_bindgen::to_value(&private_data_access)?; + + Ok(js_value) + } + + /// Upload private data to the network. + /// Uses a `Receipt` as payment. + /// + /// Returns the `PrivateDataAccess` chunk of the data. + #[wasm_bindgen(js_name = privateDataPutWithReceipt)] + pub async fn private_data_put_with_receipt( + &self, + data: Vec, + receipt: JsValue, + ) -> Result { + let data = crate::Bytes::from(data); + let receipt: Receipt = serde_wasm_bindgen::from_value(receipt)?; + let private_data_access = self.0.private_data_put(data, receipt.into()).await?; + let js_value = serde_wasm_bindgen::to_value(&private_data_access)?; + + Ok(js_value) + } + /// Fetch the data from the network. #[wasm_bindgen(js_name = dataGet)] pub async fn data_get(&self, addr: String) -> Result, JsError> { @@ -95,6 +129,16 @@ impl JsClient { Ok(data.to_vec()) } + /// Fetch the data from the network. + #[wasm_bindgen(js_name = privateDataGet)] + pub async fn private_data_get(&self, private_data_access: JsValue) -> Result, JsError> { + let private_data_access: PrivateDataAccess = + serde_wasm_bindgen::from_value(private_data_access)?; + let data = self.0.private_data_get(private_data_access).await?; + + Ok(data.to_vec()) + } + /// Get the cost of uploading data to the network. #[wasm_bindgen(js_name = dataCost)] pub async fn data_cost(&self, data: Vec) -> Result { @@ -176,6 +220,102 @@ mod archive { } } +mod archive_private { + use super::*; + use crate::client::archive_private::{PrivateArchive, PrivateArchiveAccess}; + use crate::client::data_private::PrivateDataAccess; + use crate::client::payment::Receipt; + use std::path::PathBuf; + use wasm_bindgen::JsValue; + + /// Structure mapping paths to data addresses. + #[wasm_bindgen(js_name = PrivateArchive)] + pub struct JsPrivateArchive(PrivateArchive); + + #[wasm_bindgen(js_class = PrivateArchive)] + impl JsPrivateArchive { + /// Create a new private archive. + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + Self(PrivateArchive::new()) + } + + /// Add a new file to the private archive. + #[wasm_bindgen(js_name = addNewFile)] + pub fn add_new_file(&mut self, path: String, data_map: JsValue) -> Result<(), JsError> { + let path = PathBuf::from(path); + let data_map: PrivateDataAccess = serde_wasm_bindgen::from_value(data_map)?; + self.0.add_new_file(path, data_map); + + Ok(()) + } + + #[wasm_bindgen] + pub fn map(&self) -> Result { + let files = serde_wasm_bindgen::to_value(self.0.map())?; + Ok(files) + } + } + + #[wasm_bindgen(js_class = Client)] + impl JsClient { + /// Fetch a private archive from the network. + #[wasm_bindgen(js_name = privateArchiveGet)] + pub async fn private_archive_get( + &self, + private_archive_access: JsValue, + ) -> Result { + let private_archive_access: PrivateArchiveAccess = + serde_wasm_bindgen::from_value(private_archive_access)?; + let archive = self.0.private_archive_get(private_archive_access).await?; + let archive = JsPrivateArchive(archive); + + Ok(archive) + } + + /// Upload a private archive to the network. + /// + /// Returns the `PrivateArchiveAccess` chunk of the archive. + #[wasm_bindgen(js_name = privateArchivePut)] + pub async fn private_archive_put( + &self, + archive: &JsPrivateArchive, + wallet: &JsWallet, + ) -> Result { + let private_archive_access = self + .0 + .private_archive_put(archive.0.clone(), (&wallet.0).into()) + .await?; + + let js_value = serde_wasm_bindgen::to_value(&private_archive_access)?; + + Ok(js_value) + } + + /// Upload a private archive to the network. + /// Uses a `Receipt` as payment. + /// + /// Returns the `PrivateArchiveAccess` chunk of the archive. + #[wasm_bindgen(js_name = privateArchivePutWithReceipt)] + pub async fn private_archive_put_with_receipt( + &self, + archive: &JsPrivateArchive, + receipt: JsValue, + ) -> Result { + let receipt: Receipt = serde_wasm_bindgen::from_value(receipt)?; + + let private_archive_access = self + .0 + .private_archive_put(archive.0.clone(), receipt.into()) + .await?; + + let js_value = serde_wasm_bindgen::to_value(&private_archive_access)?; + + Ok(js_value) + } + } +} + #[cfg(feature = "vault")] mod vault { use super::*; @@ -273,7 +413,7 @@ mod vault { secret_key: &SecretKeyJs, ) -> Result<(), JsError> { self.0 - .put_user_data_to_vault(&secret_key.0, &wallet.0, user_data.0.clone()) + .put_user_data_to_vault(&secret_key.0, (&wallet.0).into(), user_data.0.clone()) .await?; Ok(()) @@ -284,10 +424,12 @@ mod vault { #[cfg(feature = "external-signer")] mod external_signer { use super::*; - use crate::payment_proof_from_quotes_and_payments; + use crate::client::address::str_to_addr; + use crate::client::external_signer::encrypt_data; + use crate::client::payment::Receipt; + use crate::receipt_from_quotes_and_payments; use sn_evm::external_signer::{approve_to_spend_tokens_calldata, pay_for_quotes_calldata}; use sn_evm::EvmNetwork; - use sn_evm::ProofOfPayment; use sn_evm::QuotePayment; use sn_evm::{Amount, PaymentQuote}; use sn_evm::{EvmAddress, QuoteHash, TxHash}; @@ -298,42 +440,68 @@ mod external_signer { #[wasm_bindgen(js_class = Client)] impl JsClient { - /// Get quotes for given data. + /// Get quotes for given chunk addresses. /// /// # Example /// /// ```js - /// const [quotes, quotePayments, free_chunks] = await client.getQuotes(data); + /// const [quotes, quotePayments, free_chunks] = await client.getQuotes(chunkAddresses); /// `` #[wasm_bindgen(js_name = getQuotes)] - pub async fn get_quotes_for_data(&self, data: Vec) -> Result { - let data = crate::Bytes::from(data); - let result = self.0.get_quotes_for_data(data).await?; + pub async fn get_quotes(&self, chunk_addresses: Vec) -> Result { + let mut xor_addresses: Vec = vec![]; + + for chunk_address_str in &chunk_addresses { + let xor_address = str_to_addr(chunk_address_str)?; + xor_addresses.push(xor_address); + } + + let result = self + .0 + .get_quotes_for_content_addresses(xor_addresses.into_iter()) + .await?; + let js_value = serde_wasm_bindgen::to_value(&result)?; + Ok(js_value) } - /// Upload data with a proof of payment. + /// Upload data with a receipt. /// /// # Example /// /// ```js - /// const proof = getPaymentProofFromQuotesAndPayments(quotes, payments); - /// const addr = await client.dataPutWithProof(data, proof); + /// const receipt = getReceiptFromQuotesAndPayments(quotes, payments); + /// const addr = await client.dataPutWithReceipt(data, receipt); /// ``` - #[wasm_bindgen(js_name = dataPutWithProof)] - pub async fn data_put_with_proof_of_payment( + #[wasm_bindgen(js_name = dataPutWithReceipt)] + pub async fn data_put_with_receipt( &self, data: Vec, - proof: JsValue, + receipt: JsValue, ) -> Result { let data = crate::Bytes::from(data); - let proof: HashMap = serde_wasm_bindgen::from_value(proof)?; - let xorname = self.0.data_put_with_proof_of_payment(data, proof).await?; + let receipt: Receipt = serde_wasm_bindgen::from_value(receipt)?; + let xorname = self.0.data_put(data, receipt.into()).await?; Ok(addr_to_str(xorname)) } } + /// Encrypt data. + /// + /// # Example + /// + /// ```js + /// const [dataMapChunk, dataChunks, [chunkAddresses]] = client.encryptData(data); + /// `` + #[wasm_bindgen(js_name = encryptData)] + pub fn encrypt(data: Vec) -> Result { + let data = crate::Bytes::from(data); + let result = encrypt_data(data)?; + let js_value = serde_wasm_bindgen::to_value(&result)?; + Ok(js_value) + } + /// Get the calldata for paying for quotes. /// /// # Example @@ -370,15 +538,15 @@ mod external_signer { } /// Generate payment proof. - #[wasm_bindgen(js_name = getPaymentProofFromQuotesAndPayments)] - pub fn get_payment_proof_from_quotes_and_payments( + #[wasm_bindgen(js_name = getReceiptFromQuotesAndPayments)] + pub fn get_receipt_from_quotes_and_payments( quotes: JsValue, payments: JsValue, ) -> Result { let quotes: HashMap = serde_wasm_bindgen::from_value(quotes)?; let payments: BTreeMap = serde_wasm_bindgen::from_value(payments)?; - let proof = payment_proof_from_quotes_and_payments("es, &payments); - let js_value = serde_wasm_bindgen::to_value(&proof)?; + let receipt = receipt_from_quotes_and_payments("es, &payments); + let js_value = serde_wasm_bindgen::to_value(&receipt)?; Ok(js_value) } } diff --git a/autonomi/src/lib.rs b/autonomi/src/lib.rs index d7441a6736..2f29d04926 100644 --- a/autonomi/src/lib.rs +++ b/autonomi/src/lib.rs @@ -48,7 +48,7 @@ pub use sn_evm::EvmNetwork; pub use sn_evm::EvmWallet as Wallet; pub use sn_evm::RewardsAddress; #[cfg(feature = "external-signer")] -pub use utils::payment_proof_from_quotes_and_payments; +pub use utils::receipt_from_quotes_and_payments; #[doc(no_inline)] // Place this under 'Re-exports' in the docs. pub use bytes::Bytes; diff --git a/autonomi/src/utils.rs b/autonomi/src/utils.rs index fc9ceb7718..b664581901 100644 --- a/autonomi/src/utils.rs +++ b/autonomi/src/utils.rs @@ -1,11 +1,27 @@ +use crate::client::payment::Receipt; use sn_evm::{PaymentQuote, ProofOfPayment, QuoteHash, TxHash}; +use sn_networking::PayeeQuote; use std::collections::{BTreeMap, HashMap}; use xor_name::XorName; -pub fn payment_proof_from_quotes_and_payments( +pub fn cost_map_to_quotes( + cost_map: HashMap, +) -> HashMap { + cost_map.into_iter().map(|(k, (_, _, v))| (k, v)).collect() +} + +pub fn receipt_from_cost_map_and_payments( + cost_map: HashMap, + payments: &BTreeMap, +) -> Receipt { + let quotes = cost_map_to_quotes(cost_map); + receipt_from_quotes_and_payments("es, payments) +} + +pub fn receipt_from_quotes_and_payments( quotes: &HashMap, payments: &BTreeMap, -) -> HashMap { +) -> Receipt { quotes .iter() .filter_map(|(xor_name, quote)| { diff --git a/autonomi/tests/external_signer.rs b/autonomi/tests/external_signer.rs index d97107cb39..53dce804db 100644 --- a/autonomi/tests/external_signer.rs +++ b/autonomi/tests/external_signer.rs @@ -2,7 +2,14 @@ use alloy::network::TransactionBuilder; use alloy::providers::Provider; -use autonomi::Client; +use autonomi::client::archive::Metadata; +use autonomi::client::archive_private::PrivateArchive; +use autonomi::client::external_signer::encrypt_data; +use autonomi::client::payment::Receipt; +use autonomi::client::vault::user_data::USER_DATA_VAULT_CONTENT_IDENTIFIER; +use autonomi::client::vault::VaultSecretKey; +use autonomi::{receipt_from_quotes_and_payments, Client, Wallet}; +use bytes::Bytes; use sn_evm::{QuoteHash, TxHash}; use sn_logging::LogBuilder; use std::collections::BTreeMap; @@ -10,18 +17,21 @@ use std::time::Duration; use test_utils::evm::get_funded_wallet; use test_utils::{gen_random_data, peers_from_env}; use tokio::time::sleep; +use xor_name::XorName; -// Example of how put would be done using external signers. -#[tokio::test] -async fn external_signer_put() -> eyre::Result<()> { - let _log_appender_guard = - LogBuilder::init_single_threaded_tokio_test("external_signer_put", false); - - let client = Client::connect(&peers_from_env()?).await?; - let wallet = get_funded_wallet(); - let data = gen_random_data(1024 * 1024 * 10); +async fn pay_for_data(client: &Client, wallet: &Wallet, data: Bytes) -> eyre::Result { + let (_data_map_chunk, _chunks, xor_names) = encrypt_data(data)?; + pay_for_content_addresses(client, wallet, xor_names.into_iter()).await +} - let (quotes, quote_payments, _free_chunks) = client.get_quotes_for_data(data.clone()).await?; +async fn pay_for_content_addresses( + client: &Client, + wallet: &Wallet, + content_addrs: impl Iterator, +) -> eyre::Result { + let (quotes, quote_payments, _free_chunks) = client + .get_quotes_for_content_addresses(content_addrs) + .await?; // Form quotes payment transaction data let pay_for_quotes_calldata = autonomi::client::external_signer::pay_for_quotes_calldata( @@ -76,16 +86,100 @@ async fn external_signer_put() -> eyre::Result<()> { } // Payment proofs - let proofs = autonomi::payment_proof_from_quotes_and_payments("es, &payments); + Ok(receipt_from_quotes_and_payments("es, &payments)) +} - let addr = client - .data_put_with_proof_of_payment(data.clone(), proofs) +// Example of how put would be done using external signers. +#[tokio::test] +async fn external_signer_put() -> eyre::Result<()> { + let _log_appender_guard = + LogBuilder::init_single_threaded_tokio_test("external_signer_put", false); + + let client = Client::connect(&peers_from_env()?).await?; + let wallet = get_funded_wallet(); + let data = gen_random_data(1024 * 1024 * 10); + + let receipt = pay_for_data(&client, &wallet, data.clone()).await?; + + sleep(Duration::from_secs(5)).await; + + let private_data_access = client + .private_data_put(data.clone(), receipt.into()) .await?; - sleep(Duration::from_secs(10)).await; + let mut private_archive = PrivateArchive::new(); + private_archive.add_file("test-file".into(), private_data_access, Metadata::default()); + + let archive_serialized = private_archive.into_bytes()?; - let data_fetched = client.data_get(addr).await?; - assert_eq!(data, data_fetched, "data fetched should match data put"); + let receipt = pay_for_data(&client, &wallet, archive_serialized.clone()).await?; + + sleep(Duration::from_secs(5)).await; + + let private_archive_access = client + .private_data_put(archive_serialized, receipt.into()) + .await?; + + let vault_key = VaultSecretKey::random(); + + let mut user_data = client + .get_user_data_from_vault(&vault_key) + .await + .unwrap_or_default(); + + user_data.add_private_file_archive_with_name( + private_archive_access.clone(), + "test-archive".to_string(), + ); + + let (scratch, is_new) = client + .get_or_create_scratchpad(&vault_key, *USER_DATA_VAULT_CONTENT_IDENTIFIER) + .await?; + + assert!(is_new, "Scratchpad is not new"); + + let scratch_addresses = if is_new { + scratch.to_xor_name_vec() + } else { + vec![] + }; + + let receipt = + pay_for_content_addresses(&client, &wallet, scratch_addresses.into_iter()).await?; + + sleep(Duration::from_secs(5)).await; + + let _ = client + .put_user_data_to_vault(&vault_key, receipt.into(), user_data) + .await?; + + let fetched_user_data = client.get_user_data_from_vault(&vault_key).await?; + + let fetched_private_archive_access = fetched_user_data + .private_file_archives + .keys() + .next() + .expect("No private archive present in the UserData") + .clone(); + + let fetched_private_archive = client + .private_archive_get(fetched_private_archive_access) + .await?; + + let (_, (fetched_private_file_access, _)) = fetched_private_archive + .map() + .iter() + .next() + .expect("No file present in private archive"); + + let fetched_private_file = client + .private_data_get(fetched_private_file_access.clone()) + .await?; + + assert_eq!( + fetched_private_file, data, + "Fetched private data is not identical to the uploaded data" + ); Ok(()) } diff --git a/autonomi/tests/fs.rs b/autonomi/tests/fs.rs index b952852bc2..93fa7e3964 100644 --- a/autonomi/tests/fs.rs +++ b/autonomi/tests/fs.rs @@ -93,7 +93,12 @@ async fn file_into_vault() -> Result<()> { let archive = client.archive_get(addr).await?; let set_version = 0; client - .write_bytes_to_vault(archive.into_bytes()?, &wallet, &client_sk, set_version) + .write_bytes_to_vault( + archive.into_bytes()?, + wallet.into(), + &client_sk, + set_version, + ) .await?; // now assert over the stored account packet diff --git a/autonomi/tests/put.rs b/autonomi/tests/put.rs index dbced37d00..27bd18fafb 100644 --- a/autonomi/tests/put.rs +++ b/autonomi/tests/put.rs @@ -23,7 +23,7 @@ async fn put() -> Result<()> { let wallet = get_funded_wallet(); let data = gen_random_data(1024 * 1024 * 10); - let addr = client.data_put(data.clone(), &wallet).await?; + let addr = client.data_put(data.clone(), wallet.into()).await?; sleep(Duration::from_secs(10)).await; diff --git a/autonomi/tests/wasm.rs b/autonomi/tests/wasm.rs index 8f27576f06..70dd347ffa 100644 --- a/autonomi/tests/wasm.rs +++ b/autonomi/tests/wasm.rs @@ -25,7 +25,7 @@ async fn put() -> Result<(), Box> { let wallet = get_funded_wallet(); let data = gen_random_data(1024 * 1024 * 10); - let addr = client.data_put(data.clone(), &wallet).await?; + let addr = client.data_put(data.clone(), wallet.into()).await?; sleep(Duration::from_secs(10)).await; diff --git a/sn_node/tests/data_with_churn.rs b/sn_node/tests/data_with_churn.rs index 347c74dc44..c23248a6ba 100644 --- a/sn_node/tests/data_with_churn.rs +++ b/sn_node/tests/data_with_churn.rs @@ -338,7 +338,7 @@ fn store_chunks_task( let mut retries = 1; loop { match client - .data_put(random_data.clone(), &wallet) + .data_put(random_data.clone(), (&wallet).into()) .await .inspect_err(|err| { println!("Error to put chunk: {err:?}"); diff --git a/sn_node/tests/verify_data_location.rs b/sn_node/tests/verify_data_location.rs index 8649d07909..c043f795ce 100644 --- a/sn_node/tests/verify_data_location.rs +++ b/sn_node/tests/verify_data_location.rs @@ -332,7 +332,7 @@ async fn store_chunks( let random_bytes = Bytes::from(random_bytes); - client.data_put(random_bytes, wallet).await?; + client.data_put(random_bytes, wallet.into()).await?; uploaded_chunks_count += 1; diff --git a/sn_protocol/src/storage/scratchpad.rs b/sn_protocol/src/storage/scratchpad.rs index 94b5c633a5..1022941de2 100644 --- a/sn_protocol/src/storage/scratchpad.rs +++ b/sn_protocol/src/storage/scratchpad.rs @@ -126,11 +126,19 @@ impl Scratchpad { &self.address } - /// Returns the NetworkAddress + /// Returns the NetworkAddress. pub fn network_address(&self) -> NetworkAddress { NetworkAddress::ScratchpadAddress(self.address) } + /// Returns a VEC with the XOR name. + pub fn to_xor_name_vec(&self) -> Vec { + [self.network_address()] + .iter() + .filter_map(|f| f.as_xorname()) + .collect::>() + } + /// Returns the name. pub fn name(&self) -> XorName { self.address.xorname()