diff --git a/ant-networking/src/lib.rs b/ant-networking/src/lib.rs index d4bbd31be4..b261e05883 100644 --- a/ant-networking/src/lib.rs +++ b/ant-networking/src/lib.rs @@ -639,7 +639,7 @@ impl Network { continue; }; - if !scratchpad.is_valid() { + if !scratchpad.verify() { warn!( "Rejecting Scratchpad for {pretty_key} PUT with invalid signature" ); @@ -647,7 +647,7 @@ impl Network { } if let Some(old) = &valid_scratchpad { - if old.count() >= scratchpad.count() { + if old.counter() >= scratchpad.counter() { info!("Rejecting Scratchpad for {pretty_key} with lower count than the previous one"); continue; } diff --git a/ant-networking/src/record_store.rs b/ant-networking/src/record_store.rs index d27f98dc01..4018a69aa7 100644 --- a/ant-networking/src/record_store.rs +++ b/ant-networking/src/record_store.rs @@ -1296,12 +1296,8 @@ mod tests { // Create a scratchpad let unencrypted_scratchpad_data = Bytes::from_static(b"Test scratchpad data"); let owner_sk = SecretKey::random(); - let owner_pk = owner_sk.public_key(); - let mut scratchpad = Scratchpad::new(owner_pk, 0); - - let _next_version = - scratchpad.update_and_sign(unencrypted_scratchpad_data.clone(), &owner_sk); + let scratchpad = Scratchpad::new(&owner_sk, 0, &unencrypted_scratchpad_data, 0); let scratchpad_address = *scratchpad.address(); diff --git a/ant-node/src/error.rs b/ant-node/src/error.rs index 896185c1a8..cebe3f3ac7 100644 --- a/ant-node/src/error.rs +++ b/ant-node/src/error.rs @@ -12,6 +12,8 @@ use thiserror::Error; pub(super) type Result = std::result::Result; +const SCRATCHPAD_MAX_SIZE: usize = ant_protocol::storage::Scratchpad::MAX_SIZE; + /// Internal error. #[derive(Debug, Error)] #[allow(missing_docs)] @@ -38,12 +40,13 @@ pub enum Error { #[error("The Record::key does not match with the key derived from Record::value")] RecordKeyMismatch, - // Scratchpad is old version + // ------------ Scratchpad Errors #[error("A newer version of this Scratchpad already exists")] IgnoringOutdatedScratchpadPut, - // Scratchpad is invalid - #[error("Scratchpad signature is invalid over the counter + content hash")] + #[error("Scratchpad signature is invalid")] InvalidScratchpadSignature, + #[error("Scratchpad too big: {0}, max size is {SCRATCHPAD_MAX_SIZE}")] + ScratchpadTooBig(usize), #[error("Invalid signature")] InvalidSignature, diff --git a/ant-node/src/put_validation.rs b/ant-node/src/put_validation.rs index b652217514..df13ee7ef3 100644 --- a/ant-node/src/put_validation.rs +++ b/ant-node/src/put_validation.rs @@ -447,7 +447,7 @@ impl Node { ) -> Result<()> { // owner PK is defined herein, so as long as record key and this match, we're good let addr = scratchpad.address(); - let count = scratchpad.count(); + let count = scratchpad.counter(); debug!("Validating and storing scratchpad {addr:?} with count {count}"); // check if the deserialized value's ScratchpadAddress matches the record's key @@ -460,18 +460,24 @@ impl Node { // check if the Scratchpad is present locally that we don't have a newer version if let Some(local_pad) = self.network().get_local_record(&scratchpad_key).await? { let local_pad = try_deserialize_record::(&local_pad)?; - if local_pad.count() >= scratchpad.count() { + if local_pad.counter() >= scratchpad.counter() { warn!("Rejecting Scratchpad PUT with counter less than or equal to the current counter"); return Err(Error::IgnoringOutdatedScratchpadPut); } } // ensure data integrity - if !scratchpad.is_valid() { + if !scratchpad.verify() { warn!("Rejecting Scratchpad PUT with invalid signature"); return Err(Error::InvalidScratchpadSignature); } + // ensure the scratchpad is not too big + if scratchpad.is_too_big() { + warn!("Rejecting Scratchpad PUT with too big size"); + return Err(Error::ScratchpadTooBig(scratchpad.size())); + } + info!( "Storing sratchpad {addr:?} with content of {:?} as Record locally", scratchpad.encrypted_data_hash() diff --git a/ant-node/tests/data_with_churn.rs b/ant-node/tests/data_with_churn.rs index 3817fffedf..a6ce2029f3 100644 --- a/ant-node/tests/data_with_churn.rs +++ b/ant-node/tests/data_with_churn.rs @@ -333,7 +333,7 @@ fn create_pointers_task( PointerTarget::ChunkAddress(ChunkAddress::new(XorName(rand::random()))); loop { match client - .pointer_create(&owner, pointer_target.clone(), &wallet) + .pointer_create(&owner, pointer_target.clone(), (&wallet).into()) .await { Ok((cost, addr)) => { diff --git a/ant-protocol/src/storage/graph.rs b/ant-protocol/src/storage/graph.rs index ea21cecff3..c2c06a120b 100644 --- a/ant-protocol/src/storage/graph.rs +++ b/ant-protocol/src/storage/graph.rs @@ -28,6 +28,9 @@ pub struct GraphEntry { } impl GraphEntry { + /// Maximum size of a graph entry + pub const MAX_SIZE: usize = 1024; + /// Create a new graph entry, signing it with the provided secret key. pub fn new( owner: PublicKey, @@ -101,8 +104,29 @@ impl GraphEntry { Self::bytes_to_sign(&self.owner, &self.parents, &self.content, &self.outputs) } + /// Verify the signature of the graph entry pub fn verify(&self) -> bool { self.owner .verify(&self.signature, self.bytes_for_signature()) } + + /// Size of the graph entry + pub fn size(&self) -> usize { + size_of::() + + self + .outputs + .iter() + .map(|(p, c)| p.to_bytes().len() + c.len()) + .sum::() + + self + .parents + .iter() + .map(|p| p.to_bytes().len()) + .sum::() + } + + /// Returns true if the graph entry is too big + pub fn is_too_big(&self) -> bool { + self.size() > Self::MAX_SIZE + } } diff --git a/ant-protocol/src/storage/pointer.rs b/ant-protocol/src/storage/pointer.rs index 54f2957998..04ef756dc2 100644 --- a/ant-protocol/src/storage/pointer.rs +++ b/ant-protocol/src/storage/pointer.rs @@ -27,6 +27,8 @@ pub enum PointerError { SerializationError(String), } +/// Pointer, a mutable address pointing to other data on the Network +/// It is stored at the owner's public key and can only be updated by the owner #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Pointer { owner: PublicKey, @@ -130,6 +132,11 @@ impl Pointer { let bytes = self.bytes_for_signature(); self.owner.verify(&self.signature, &bytes) } + + /// Size of the pointer + pub fn size() -> usize { + size_of::() + } } #[cfg(test)] diff --git a/ant-protocol/src/storage/scratchpad.rs b/ant-protocol/src/storage/scratchpad.rs index 9c40b4dbc5..31a87c7d9f 100644 --- a/ant-protocol/src/storage/scratchpad.rs +++ b/ant-protocol/src/storage/scratchpad.rs @@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize}; use xor_name::XorName; -/// Scratchpad, a mutable address for encrypted data +/// Scratchpad, a mutable space for encrypted data on the Network #[derive( Hash, Eq, PartialEq, PartialOrd, Ord, Clone, custom_debug::Debug, Serialize, Deserialize, )] @@ -25,30 +25,81 @@ pub struct Scratchpad { address: ScratchpadAddress, /// Data encoding: custom apps using scratchpad should use this so they can identify the type of data they are storing data_encoding: u64, - /// Contained data. This should be encrypted + /// Encrypted data stored in the scratchpad, it is encrypted automatically by the [`Scratchpad::new`] and [`Scratchpad::update`] methods #[debug(skip)] encrypted_data: Bytes, /// Monotonically increasing counter to track the number of times this has been updated. + /// When pushed to the network, the scratchpad with the highest counter is kept. counter: u64, - /// Signature over `Vec`.extend(Xorname::from_content(encrypted_data).to_vec()) from the owning key. - /// Required for scratchpad to be valid. - signature: Option, + /// Signature over the above fields + signature: Signature, } impl Scratchpad { - /// Creates a new instance of `Scratchpad`. - pub fn new(owner: PublicKey, data_encoding: u64) -> Self { + /// Max Scratchpad size is 4MB including the metadata + pub const MAX_SIZE: usize = 4 * 1024 * 1024; + + /// Creates a new instance of `Scratchpad`. Encrypts the data, and signs all the elements. + pub fn new( + owner: &SecretKey, + data_encoding: u64, + unencrypted_data: &Bytes, + counter: u64, + ) -> Self { + let pk = owner.public_key(); + let encrypted_data = Bytes::from(pk.encrypt(unencrypted_data).to_bytes()); + let addr = ScratchpadAddress::new(pk); + let signature = owner.sign(Self::bytes_for_signature( + addr, + data_encoding, + &encrypted_data, + counter, + )); + Self { + address: addr, + encrypted_data, + data_encoding, + counter, + signature, + } + } + + /// Create a new Scratchpad without provding the secret key + /// It is the caller's responsibility to ensure the signature is valid (signs [`Scratchpad::bytes_for_signature`]) and the data is encrypted + /// It is recommended to use the [`Scratchpad::new`] method instead when possible + pub fn new_with_signature( + owner: PublicKey, + data_encoding: u64, + encrypted_data: Bytes, + counter: u64, + signature: Signature, + ) -> Self { Self { address: ScratchpadAddress::new(owner), - encrypted_data: Bytes::new(), + encrypted_data, data_encoding, - counter: 0, - signature: None, + counter, + signature, } } - /// Return the current count - pub fn count(&self) -> u64 { + /// Returns the bytes to sign for the signature + pub fn bytes_for_signature( + address: ScratchpadAddress, + data_encoding: u64, + encrypted_data: &Bytes, + counter: u64, + ) -> Vec { + let mut bytes_to_sign = data_encoding.to_be_bytes().to_vec(); + bytes_to_sign.extend(address.to_hex().as_bytes()); + bytes_to_sign.extend(counter.to_be_bytes().to_vec()); + bytes_to_sign.extend(encrypted_data.to_vec()); + bytes_to_sign + } + + /// Get the counter of the Scratchpad, the higher the counter, the more recent the Scratchpad is + /// Similarly to counter CRDTs only the latest version (highest counter) of the Scratchpad is kept on the network + pub fn counter(&self) -> u64 { self.counter } @@ -57,43 +108,32 @@ impl Scratchpad { self.data_encoding } - /// Increments the counter value. - pub fn increment(&mut self) -> u64 { + /// Updates the content and encrypts it, increments the counter, re-signs the scratchpad + pub fn update(&mut self, unencrypted_data: &Bytes, sk: &SecretKey) { self.counter += 1; - - self.counter - } - - /// Returns the next counter value, - /// - /// Encrypts data and updates the signature with provided sk - pub fn update_and_sign(&mut self, unencrypted_data: Bytes, sk: &SecretKey) -> u64 { - let next_count = self.increment(); - let pk = self.owner(); - + let address = ScratchpadAddress::new(*pk); self.encrypted_data = Bytes::from(pk.encrypt(unencrypted_data).to_bytes()); - let encrypted_data_xorname = self.encrypted_data_hash().to_vec(); - - let mut bytes_to_sign = self.counter.to_be_bytes().to_vec(); - bytes_to_sign.extend(encrypted_data_xorname); - - self.signature = Some(sk.sign(&bytes_to_sign)); - next_count + let bytes_to_sign = Self::bytes_for_signature( + address, + self.data_encoding, + &self.encrypted_data, + self.counter, + ); + self.signature = sk.sign(&bytes_to_sign); + debug_assert!(self.verify(), "Must be valid after being signed. This is a bug, please report it by opening an issue on our github"); } - /// Verifies the signature and content of the scratchpad are valid for the - /// owner's public key. - pub fn is_valid(&self) -> bool { - if let Some(signature) = &self.signature { - let mut signing_bytes = self.counter.to_be_bytes().to_vec(); - signing_bytes.extend(self.encrypted_data_hash().to_vec()); // add the count - - self.owner().verify(signature, &signing_bytes) - } else { - false - } + /// Verifies that the Scratchpad signature is valid + pub fn verify(&self) -> bool { + let signing_bytes = Self::bytes_for_signature( + self.address, + self.data_encoding, + &self.encrypted_data, + self.counter, + ); + self.owner().verify(&self.signature, &signing_bytes) } /// Returns the encrypted_data. @@ -140,6 +180,16 @@ impl Scratchpad { pub fn payload_size(&self) -> usize { self.encrypted_data.len() } + + /// Size of the scratchpad + pub fn size(&self) -> usize { + size_of::() + self.payload_size() + } + + /// Returns true if the scratchpad is too big + pub fn is_too_big(&self) -> bool { + self.size() > Self::MAX_SIZE + } } #[cfg(test)] @@ -147,11 +197,29 @@ mod tests { use super::*; #[test] - fn test_scratchpad_is_valid() { + fn test_scratchpad_sig_and_update() { + let sk = SecretKey::random(); + let raw_data = Bytes::from_static(b"data to be encrypted"); + let mut scratchpad = Scratchpad::new(&sk, 42, &raw_data, 0); + assert!(scratchpad.verify()); + assert_eq!(scratchpad.counter(), 0); + assert_ne!(scratchpad.encrypted_data(), &raw_data); + + let raw_data2 = Bytes::from_static(b"data to be encrypted v2"); + scratchpad.update(&raw_data2, &sk); + assert!(scratchpad.verify()); + assert_eq!(scratchpad.counter(), 1); + assert_ne!(scratchpad.encrypted_data(), &raw_data); + assert_ne!(scratchpad.encrypted_data(), &raw_data2); + } + + #[test] + fn test_scratchpad_encryption() { let sk = SecretKey::random(); - let pk = sk.public_key(); - let mut scratchpad = Scratchpad::new(pk, 42); - scratchpad.update_and_sign(Bytes::from_static(b"data to be encrypted"), &sk); - assert!(scratchpad.is_valid()); + let raw_data = Bytes::from_static(b"data to be encrypted"); + let scratchpad = Scratchpad::new(&sk, 42, &raw_data, 0); + + let decrypted_data = scratchpad.decrypt_data(&sk).unwrap(); + assert_eq!(decrypted_data, raw_data); } } diff --git a/autonomi/src/client/data_types/graph.rs b/autonomi/src/client/data_types/graph.rs index 8c480f09c0..073709c10d 100644 --- a/autonomi/src/client/data_types/graph.rs +++ b/autonomi/src/client/data_types/graph.rs @@ -7,12 +7,13 @@ // permissions and limitations relating to use of the SAFE Network Software. use crate::client::payment::PayError; +use crate::client::payment::PaymentOption; use crate::client::quote::CostError; use crate::client::Client; use crate::client::ClientEvent; use crate::client::UploadSummary; -use ant_evm::{Amount, AttoTokens, EvmWallet, EvmWalletError}; +use ant_evm::{Amount, AttoTokens, EvmWalletError}; use ant_networking::{GetRecordCfg, NetworkError, PutRecordCfg, VerificationKind}; use ant_protocol::{ storage::{try_serialize_record, DataTypes, RecordKind, RetryStrategy}, @@ -64,7 +65,7 @@ impl Client { pub async fn graph_entry_put( &self, entry: GraphEntry, - wallet: &EvmWallet, + payment_option: PaymentOption, ) -> Result<(AttoTokens, GraphEntryAddress), GraphError> { let address = entry.address(); @@ -72,10 +73,10 @@ impl Client { let xor_name = address.xorname(); debug!("Paying for graph entry at address: {address:?}"); let (payment_proofs, skipped_payments) = self - .pay( - DataTypes::GraphEntry.get_index(), - std::iter::once((*xor_name, entry.bytes_for_signature().len())), - wallet, + .pay_for_content_addrs( + DataTypes::GraphEntry, + std::iter::once((*xor_name, entry.size())), + payment_option, ) .await .inspect_err(|err| { @@ -148,11 +149,10 @@ impl Client { trace!("Getting cost for GraphEntry of {key:?}"); let address = GraphEntryAddress::from_owner(key); let xor = *address.xorname(); - // TODO: define default size of GraphEntry let store_quote = self .get_store_quotes( - DataTypes::GraphEntry.get_index(), - std::iter::once((xor, 512)), + DataTypes::GraphEntry, + std::iter::once((xor, GraphEntry::MAX_SIZE)), ) .await?; let total_cost = AttoTokens::from_atto( diff --git a/autonomi/src/client/data_types/pointer.rs b/autonomi/src/client/data_types/pointer.rs index b1579aa3db..15d3a80b9c 100644 --- a/autonomi/src/client/data_types/pointer.rs +++ b/autonomi/src/client/data_types/pointer.rs @@ -6,8 +6,12 @@ // 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::PayError, quote::CostError, Client}; -use ant_evm::{Amount, AttoTokens, EvmWallet, EvmWalletError}; +use crate::client::{ + payment::{PayError, PaymentOption}, + quote::CostError, + Client, +}; +use ant_evm::{Amount, AttoTokens, EvmWalletError}; use ant_networking::{GetRecordCfg, GetRecordError, NetworkError, PutRecordCfg, VerificationKind}; use ant_protocol::{ storage::{ @@ -22,6 +26,7 @@ use tracing::{debug, error, trace}; pub use ant_protocol::storage::{Pointer, PointerAddress, PointerTarget}; +/// Errors that can occur when dealing with Pointers #[derive(Debug, thiserror::Error)] pub enum PointerError { #[error("Cost error: {0}")] @@ -83,11 +88,11 @@ impl Client { } } - /// Store a pointer on the network + /// Manually store a pointer on the network pub async fn pointer_put( &self, pointer: Pointer, - wallet: &EvmWallet, + payment_option: PaymentOption, ) -> Result<(AttoTokens, PointerAddress), PointerError> { let address = pointer.network_address(); @@ -96,10 +101,10 @@ impl Client { debug!("Paying for pointer at address: {address:?}"); let (payment_proofs, _skipped_payments) = self // TODO: define Pointer default size for pricing - .pay( - DataTypes::Pointer.get_index(), - std::iter::once((xor_name, 128)), - wallet, + .pay_for_content_addrs( + DataTypes::Pointer, + std::iter::once((xor_name, Pointer::size())), + payment_option, ) .await .inspect_err(|err| { @@ -174,7 +179,7 @@ impl Client { &self, owner: &SecretKey, target: PointerTarget, - wallet: &EvmWallet, + payment_option: PaymentOption, ) -> Result<(AttoTokens, PointerAddress), PointerError> { let address = PointerAddress::from_owner(owner.public_key()); let already_exists = match self.pointer_get(address).await { @@ -193,7 +198,7 @@ impl Client { } let pointer = Pointer::new(owner, 0, target); - self.pointer_put(pointer, wallet).await + self.pointer_put(pointer, payment_option).await } /// Update an existing pointer to point to a new target on the network @@ -269,9 +274,8 @@ impl Client { let address = PointerAddress::from_owner(key); let xor = *address.xorname(); - // TODO: define default size of Pointer let store_quote = self - .get_store_quotes(DataTypes::Pointer.get_index(), std::iter::once((xor, 128))) + .get_store_quotes(DataTypes::Pointer, std::iter::once((xor, Pointer::size()))) .await?; let total_cost = AttoTokens::from_atto( store_quote diff --git a/autonomi/src/client/data_types/scratchpad.rs b/autonomi/src/client/data_types/scratchpad.rs index 916474c817..38116934c0 100644 --- a/autonomi/src/client/data_types/scratchpad.rs +++ b/autonomi/src/client/data_types/scratchpad.rs @@ -6,10 +6,9 @@ // 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::PaymentOption; -use crate::client::PutError; +use crate::client::payment::{PayError, PaymentOption}; use crate::{client::quote::CostError, Client}; -use ant_evm::{Amount, AttoTokens}; +use crate::{Amount, AttoTokens}; use ant_networking::{GetRecordCfg, GetRecordError, NetworkError, PutRecordCfg, VerificationKind}; use ant_protocol::storage::{try_serialize_record, RecordKind, RetryStrategy}; use ant_protocol::{ @@ -19,17 +18,33 @@ use ant_protocol::{ use libp2p::kad::{Quorum, Record}; use std::collections::HashSet; +pub use crate::Bytes; pub use ant_protocol::storage::{Scratchpad, ScratchpadAddress}; -pub use bls::{PublicKey, SecretKey}; +pub use bls::{PublicKey, SecretKey, Signature}; +const SCRATCHPAD_MAX_SIZE: usize = Scratchpad::MAX_SIZE; + +/// Errors that can occur when dealing with Scratchpads #[derive(Debug, thiserror::Error)] pub enum ScratchpadError { + #[error("Payment failure occurred during scratchpad creation.")] + Pay(#[from] PayError), #[error("Scratchpad found at {0:?} was not a valid record.")] CouldNotDeserializeScratchPad(ScratchpadAddress), #[error("Network: {0}")] Network(#[from] NetworkError), #[error("Scratchpad not found")] Missing, + #[error("Serialization error")] + Serialization, + #[error("Scratchpad already exists at this address: {0:?}")] + ScratchpadAlreadyExists(ScratchpadAddress), + #[error("Scratchpad cannot be updated as it does not exist, please create it first or wait for it to be created")] + CannotUpdateNewScratchpad, + #[error("Scratchpad size is too big: {0} > {SCRATCHPAD_MAX_SIZE}")] + ScratchpadTooBig(usize), + #[error("Scratchpad signature is not valid")] + BadSignature, } impl Client { @@ -79,14 +94,14 @@ impl Client { .map_err(|_| ScratchpadError::CouldNotDeserializeScratchPad(*address))?; // take the latest versions - pads.sort_by_key(|s| s.count()); - let max_version = pads.last().map(|p| p.count()).unwrap_or_else(|| { + pads.sort_by_key(|s| s.counter()); + let max_version = pads.last().map(|p| p.counter()).unwrap_or_else(|| { error!("Got empty scratchpad vector for {scratch_key:?}"); u64::MAX }); let latest_pads: Vec<_> = pads .into_iter() - .filter(|s| s.count() == max_version) + .filter(|s| s.counter() == max_version) .collect(); // make sure we only have one of latest version @@ -112,170 +127,219 @@ impl Client { Ok(pad) } - /// Returns the latest found version of the scratchpad for that secret key - /// If none is found, it creates a new one locally - /// Note that is does not upload that new scratchpad to the network, one would need to call [`Self::scratchpad_create`] to do so - /// Returns the scratchpad along with a boolean indicating if that scratchpad is new or not - pub async fn get_or_create_scratchpad( - &self, - public_key: &PublicKey, - content_type: u64, - ) -> Result<(Scratchpad, bool), PutError> { - let pad_res = self.scratchpad_get_from_public_key(public_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() != public_key { - return Err(PutError::ScratchpadBadOwner); - } - - existing_data - } else { - trace!("new scratchpad creation"); - Scratchpad::new(*public_key, content_type) - }; - - Ok((scratch, is_new)) + /// Verify a scratchpad + pub fn scratchpad_verify(scratchpad: &Scratchpad) -> Result<(), ScratchpadError> { + if !scratchpad.verify() { + return Err(ScratchpadError::BadSignature); + } + if scratchpad.is_too_big() { + return Err(ScratchpadError::ScratchpadTooBig(scratchpad.size())); + } + Ok(()) } - /// Create a new scratchpad to the network - /// Returns the cost of the scratchpad and the address of the scratchpad - pub async fn scratchpad_create( + /// Manually store a scratchpad on the network + pub async fn scratchpad_put( &self, scratchpad: Scratchpad, payment_option: PaymentOption, - ) -> Result<(AttoTokens, ScratchpadAddress), PutError> { - let scratch_address = scratchpad.network_address(); - let address = *scratchpad.address(); - let scratch_key = scratch_address.to_record_key(); + ) -> Result<(AttoTokens, ScratchpadAddress), ScratchpadError> { + let address = scratchpad.address(); + Self::scratchpad_verify(&scratchpad)?; // pay for the scratchpad - let (receipt, _skipped_payments) = self + let xor_name = address.xorname(); + debug!("Paying for scratchpad at address: {address:?}"); + let (payment_proofs, _skipped_payments) = self .pay_for_content_addrs( - DataTypes::Scratchpad.get_index(), - std::iter::once((scratchpad.xorname(), scratchpad.payload_size())), + DataTypes::Scratchpad, + std::iter::once((xor_name, scratchpad.size())), payment_option, ) .await .inspect_err(|err| { - error!("Failed to pay for new scratchpad at addr: {scratch_address:?} : {err}"); + error!("Failed to pay for scratchpad at address: {address:?} : {err}") })?; - let (proof, price) = match receipt.values().next() { - Some(proof) => proof, - None => return Err(PutError::PaymentUnexpectedlyInvalid(scratch_address)), + // verify payment was successful + let (proof, price) = match payment_proofs.get(&xor_name) { + Some((proof, price)) => (Some(proof), price), + None => { + info!("Scratchpad at address: {address:?} was already paid for, update is free"); + (None, &AttoTokens::zero()) + } }; let total_cost = *price; - let record = Record { - key: scratch_key, - value: try_serialize_record( - &(proof, scratchpad), - RecordKind::DataWithPayment(DataTypes::Scratchpad), - ) - .map_err(|_| { - PutError::Serialization("Failed to serialize scratchpad with payment".to_string()) - })? - .to_vec(), - publisher: None, - expires: None, + let net_addr = NetworkAddress::from_scratchpad_address(*address); + let (record, payees) = if let Some(proof) = proof { + let payees = Some(proof.payees()); + let record = Record { + key: net_addr.to_record_key(), + value: try_serialize_record( + &(proof, &scratchpad), + RecordKind::DataWithPayment(DataTypes::Scratchpad), + ) + .map_err(|_| ScratchpadError::Serialization)? + .to_vec(), + publisher: None, + expires: None, + }; + (record, payees) + } else { + let record = Record { + key: net_addr.to_record_key(), + value: try_serialize_record( + &scratchpad, + RecordKind::DataOnly(DataTypes::Scratchpad), + ) + .map_err(|_| ScratchpadError::Serialization)? + .to_vec(), + publisher: None, + expires: None, + }; + (record, None) + }; + + let get_cfg = GetRecordCfg { + get_quorum: Quorum::Majority, + retry_strategy: Some(RetryStrategy::default()), + target_record: None, + expected_holders: Default::default(), }; let put_cfg = PutRecordCfg { - put_quorum: Quorum::Majority, - retry_strategy: Some(RetryStrategy::Balanced), - use_put_record_to: None, - verification: Some(( - VerificationKind::Crdt, - GetRecordCfg { - get_quorum: Quorum::Majority, - retry_strategy: None, - target_record: None, - expected_holders: HashSet::new(), - }, - )), + put_quorum: Quorum::All, + retry_strategy: None, + verification: Some((VerificationKind::Crdt, get_cfg)), + use_put_record_to: payees, }; - debug!("Put record - scratchpad at {scratch_address:?} to the network"); + // store the scratchpad on the network + debug!("Storing scratchpad at address {address:?} to the network"); self.network .put_record(record, &put_cfg) .await .inspect_err(|err| { - error!( - "Failed to put scratchpad {scratch_address:?} to the network with err: {err:?}" - ) + error!("Failed to put record - scratchpad {address:?} to the network: {err}") })?; - Ok((total_cost, address)) + Ok((total_cost, *address)) + } + + /// Create a new scratchpad to the network + /// Make sure that the owner key is not already used for another scratchpad as each key is associated with one scratchpad + /// The data will be encrypted with the owner key before being stored on the network + /// The content type is used to identify the type of data stored in the scratchpad, the choice is up to the caller + /// Returns the cost and the address of the scratchpad + pub async fn scratchpad_create( + &self, + owner: &SecretKey, + content_type: u64, + initial_data: &Bytes, + payment_option: PaymentOption, + ) -> Result<(AttoTokens, ScratchpadAddress), ScratchpadError> { + let address = ScratchpadAddress::new(owner.public_key()); + let already_exists = match self.scratchpad_get(&address).await { + Ok(_) => true, + Err(ScratchpadError::Network(NetworkError::GetRecordError( + GetRecordError::SplitRecord { .. }, + ))) => true, + Err(ScratchpadError::Network(NetworkError::GetRecordError( + GetRecordError::RecordNotFound, + ))) => false, + Err(err) => return Err(err), + }; + + if already_exists { + return Err(ScratchpadError::ScratchpadAlreadyExists(address)); + } + + let counter = 0; + let scratchpad = Scratchpad::new(owner, content_type, initial_data, counter); + self.scratchpad_put(scratchpad, payment_option).await } /// Update an existing scratchpad to the network /// This operation is free but requires the scratchpad to be already created on the network - /// Only the latest version of the scratchpad is kept, make sure to update the scratchpad counter before calling this function - /// The method [`Scratchpad::update_and_sign`] should be used before calling this function to send the scratchpad to the network - pub async fn scratchpad_update(&self, scratchpad: Scratchpad) -> Result<(), PutError> { - let scratch_address = scratchpad.network_address(); - let scratch_key = scratch_address.to_record_key(); + /// Only the latest version of the scratchpad is kept on the Network, previous versions will be overwritten and unrecoverable + pub async fn scratchpad_update( + &self, + owner: &SecretKey, + content_type: u64, + data: &Bytes, + ) -> Result<(), ScratchpadError> { + let address = ScratchpadAddress::new(owner.public_key()); + let current = match self.scratchpad_get(&address).await { + Ok(scratchpad) => Some(scratchpad), + Err(ScratchpadError::Network(NetworkError::GetRecordError( + GetRecordError::RecordNotFound, + ))) => None, + Err(ScratchpadError::Network(NetworkError::GetRecordError( + GetRecordError::SplitRecord { result_map }, + ))) => result_map + .values() + .filter_map(|(record, _)| try_deserialize_record::(record).ok()) + .max_by_key(|scratchpad: &Scratchpad| scratchpad.counter()), + Err(err) => { + return Err(err); + } + }; - let put_cfg = PutRecordCfg { - put_quorum: Quorum::Majority, - retry_strategy: Some(RetryStrategy::Balanced), - use_put_record_to: None, - verification: Some(( - VerificationKind::Crdt, - GetRecordCfg { - get_quorum: Quorum::Majority, - retry_strategy: None, - target_record: None, - expected_holders: HashSet::new(), - }, - )), + let scratchpad = if let Some(p) = current { + let version = p.counter() + 1; + Scratchpad::new(owner, content_type, data, version) + } else { + warn!("Scratchpad at address {address:?} cannot be updated as it does not exist, please create it first or wait for it to be created"); + return Err(ScratchpadError::CannotUpdateNewScratchpad); }; + // make sure the scratchpad is valid + Self::scratchpad_verify(&scratchpad)?; + + // prepare the record to be stored let record = Record { - key: scratch_key, + key: NetworkAddress::from_scratchpad_address(address).to_record_key(), value: try_serialize_record(&scratchpad, RecordKind::DataOnly(DataTypes::Scratchpad)) - .map_err(|_| PutError::Serialization("Failed to serialize scratchpad".to_string()))? + .map_err(|_| ScratchpadError::Serialization)? .to_vec(), publisher: None, expires: None, }; + let get_cfg = GetRecordCfg { + get_quorum: Quorum::Majority, + retry_strategy: Some(RetryStrategy::default()), + target_record: None, + expected_holders: Default::default(), + }; + let put_cfg = PutRecordCfg { + put_quorum: Quorum::All, + retry_strategy: None, + verification: Some((VerificationKind::Crdt, get_cfg)), + use_put_record_to: None, + }; - debug!("Put record - scratchpad at {scratch_address:?} to the network"); + // store the scratchpad on the network + debug!("Updating scratchpad at address {address:?} to the network"); self.network .put_record(record, &put_cfg) .await .inspect_err(|err| { - error!( - "Failed to put scratchpad {scratch_address:?} to the network with err: {err:?}" - ) + error!("Failed to update scratchpad at address {address:?} to the network: {err}") })?; Ok(()) } /// Get the cost of creating a new Scratchpad - pub async fn scratchpad_cost(&self, owner: &SecretKey) -> Result { + pub async fn scratchpad_cost(&self, owner: &PublicKey) -> Result { info!("Getting cost for scratchpad"); - let client_pk = owner.public_key(); - let content_type = Default::default(); - let scratch = Scratchpad::new(client_pk, content_type); - let scratch_xor = scratch.address().xorname(); + let scratch_xor = ScratchpadAddress::new(*owner).xorname(); - // TODO: define default size of Scratchpad let store_quote = self .get_store_quotes( - DataTypes::Scratchpad.get_index(), - std::iter::once((scratch_xor, 256)), + DataTypes::Scratchpad, + std::iter::once((scratch_xor, SCRATCHPAD_MAX_SIZE)), ) .await?; diff --git a/autonomi/src/client/external_signer.rs b/autonomi/src/client/external_signer.rs index ef201083ca..24c23ae35a 100644 --- a/autonomi/src/client/external_signer.rs +++ b/autonomi/src/client/external_signer.rs @@ -1,3 +1,4 @@ +use crate::client::quote::DataTypes; use crate::client::PutError; use crate::self_encryption::encrypt; use crate::Client; @@ -17,7 +18,7 @@ impl Client { /// Returns a cost map, data payments to be executed and a list of free (already paid for) chunks. pub async fn get_quotes_for_content_addresses( &self, - data_type: u32, + data_type: DataTypes, content_addrs: impl Iterator + Clone, ) -> Result< ( diff --git a/autonomi/src/client/high_level/data/private.rs b/autonomi/src/client/high_level/data/private.rs index f3e6c3d8a7..a7be3a51d5 100644 --- a/autonomi/src/client/high_level/data/private.rs +++ b/autonomi/src/client/high_level/data/private.rs @@ -77,11 +77,7 @@ impl Client { .collect(); info!("Paying for {} addresses", xor_names.len()); let (receipt, skipped_payments) = self - .pay_for_content_addrs( - DataTypes::Chunk.get_index(), - xor_names.into_iter(), - payment_option, - ) + .pay_for_content_addrs(DataTypes::Chunk, xor_names.into_iter(), payment_option) .await .inspect_err(|err| error!("Error paying for data: {err:?}"))?; diff --git a/autonomi/src/client/high_level/data/public.rs b/autonomi/src/client/high_level/data/public.rs index d53798dad1..7b4fb0f3b7 100644 --- a/autonomi/src/client/high_level/data/public.rs +++ b/autonomi/src/client/high_level/data/public.rs @@ -54,11 +54,7 @@ impl Client { // Pay for all chunks + data map chunk info!("Paying for {} addresses", xor_names.len()); let (receipt, skipped_payments) = self - .pay_for_content_addrs( - DataTypes::Chunk.get_index(), - xor_names.into_iter(), - payment_option, - ) + .pay_for_content_addrs(DataTypes::Chunk, xor_names.into_iter(), payment_option) .await .inspect_err(|err| error!("Error paying for data: {err:?}"))?; @@ -127,7 +123,7 @@ impl Client { ); let store_quote = self - .get_store_quotes(DataTypes::Chunk.get_index(), content_addrs.into_iter()) + .get_store_quotes(DataTypes::Chunk, content_addrs.into_iter()) .await .inspect_err(|err| error!("Error getting store quotes: {err:?}"))?; diff --git a/autonomi/src/client/high_level/vault/mod.rs b/autonomi/src/client/high_level/vault/mod.rs index 65468b55b3..f96c09aec6 100644 --- a/autonomi/src/client/high_level/vault/mod.rs +++ b/autonomi/src/client/high_level/vault/mod.rs @@ -9,13 +9,14 @@ pub mod key; pub mod user_data; +use ant_protocol::storage::ScratchpadAddress; pub use key::{derive_vault_key, VaultSecretKey}; pub use user_data::UserData; use crate::client::data_types::scratchpad::ScratchpadError; use crate::client::payment::PaymentOption; use crate::client::quote::CostError; -use crate::client::{Client, PutError}; +use crate::client::Client; use ant_evm::AttoTokens; use ant_protocol::Bytes; use std::hash::{DefaultHasher, Hash, Hasher}; @@ -62,7 +63,7 @@ impl Client { /// Get the cost of creating a new vault pub async fn vault_cost(&self, owner: &VaultSecretKey) -> Result { info!("Getting cost for vault"); - self.scratchpad_cost(owner).await + self.scratchpad_cost(&owner.public_key()).await } /// Put data into the client's VaultPacket @@ -76,27 +77,22 @@ impl Client { payment_option: PaymentOption, secret_key: &VaultSecretKey, content_type: VaultContentType, - ) -> Result { - let public_key = secret_key.public_key(); - let (mut scratch, is_new) = self - .get_or_create_scratchpad(&public_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"); - - let scratch_address = scratch.network_address(); - + ) -> Result { + let scratch_address = ScratchpadAddress::new(secret_key.public_key()); info!("Writing to vault at {scratch_address:?}"); - let total_cost = if is_new { - let (cost, _address) = self.scratchpad_create(scratch, payment_option).await?; - cost - } else { - self.scratchpad_update(scratch).await?; - AttoTokens::zero() - }; - - Ok(total_cost) + match self + .scratchpad_update(secret_key, content_type, &data) + .await + { + Ok(()) => Ok(AttoTokens::zero()), + Err(ScratchpadError::CannotUpdateNewScratchpad) => { + let (price, _) = self + .scratchpad_create(secret_key, content_type, &data, payment_option) + .await?; + Ok(price) + } + Err(err) => Err(err.into()), + } } } diff --git a/autonomi/src/client/high_level/vault/user_data.rs b/autonomi/src/client/high_level/vault/user_data.rs index 2572383bde..2d5d681ee5 100644 --- a/autonomi/src/client/high_level/vault/user_data.rs +++ b/autonomi/src/client/high_level/vault/user_data.rs @@ -12,7 +12,7 @@ use crate::client::high_level::files::archive_private::PrivateArchiveAccess; use crate::client::high_level::files::archive_public::ArchiveAddr; use crate::client::payment::PaymentOption; use crate::client::Client; -use crate::client::{GetError, PutError}; +use crate::client::GetError; use ant_evm::AttoTokens; use ant_protocol::Bytes; use serde::{Deserialize, Serialize}; @@ -38,7 +38,7 @@ pub struct UserData { /// Errors that can occur during the get operation. #[derive(Debug, thiserror::Error)] -pub enum UserDataVaultGetError { +pub enum UserDataVaultError { #[error("Vault error: {0}")] Vault(#[from] VaultError), #[error("Unsupported vault content type: {0}")] @@ -111,19 +111,17 @@ impl Client { pub async fn get_user_data_from_vault( &self, secret_key: &VaultSecretKey, - ) -> Result { + ) -> Result { let (bytes, content_type) = self.fetch_and_decrypt_vault(secret_key).await?; if content_type != *USER_DATA_VAULT_CONTENT_IDENTIFIER { - return Err(UserDataVaultGetError::UnsupportedVaultContentType( + return Err(UserDataVaultError::UnsupportedVaultContentType( content_type, )); } let vault = UserData::from_bytes(bytes).map_err(|e| { - UserDataVaultGetError::Serialization(format!( - "Failed to deserialize vault content: {e}" - )) + UserDataVaultError::Serialization(format!("Failed to deserialize vault content: {e}")) })?; Ok(vault) @@ -136,10 +134,10 @@ impl Client { secret_key: &VaultSecretKey, payment_option: PaymentOption, user_data: UserData, - ) -> Result { - let bytes = user_data - .to_bytes() - .map_err(|e| PutError::Serialization(format!("Failed to serialize user data: {e}")))?; + ) -> Result { + let bytes = user_data.to_bytes().map_err(|e| { + UserDataVaultError::Serialization(format!("Failed to serialize user data: {e}")) + })?; let total_cost = self .write_bytes_to_vault( bytes, diff --git a/autonomi/src/client/payment.rs b/autonomi/src/client/payment.rs index a1bdc6802e..f1c652d63e 100644 --- a/autonomi/src/client/payment.rs +++ b/autonomi/src/client/payment.rs @@ -1,4 +1,4 @@ -use crate::client::quote::StoreQuote; +use crate::client::quote::{DataTypes, StoreQuote}; use crate::Client; use ant_evm::{AttoTokens, EncodedPeerId, EvmWallet, EvmWalletError, ProofOfPayment}; use std::collections::HashMap; @@ -56,7 +56,9 @@ pub fn receipt_from_store_quotes(quotes: StoreQuote) -> Receipt { /// Payment options for data payments. #[derive(Clone)] pub enum PaymentOption { + /// Pay using an evm wallet Wallet(EvmWallet), + /// When data was already paid for, use the receipt Receipt(Receipt), } @@ -81,7 +83,7 @@ impl From for PaymentOption { impl Client { pub(crate) async fn pay_for_content_addrs( &self, - data_type: u32, + data_type: DataTypes, content_addrs: impl Iterator + Clone, payment_option: PaymentOption, ) -> Result<(Receipt, AlreadyPaidAddressesCount), PayError> { @@ -97,7 +99,7 @@ impl Client { /// Pay for the chunks and get the proof of payment. pub(crate) async fn pay( &self, - data_type: u32, + data_type: DataTypes, content_addrs: impl Iterator + Clone, wallet: &EvmWallet, ) -> Result<(Receipt, AlreadyPaidAddressesCount), PayError> { diff --git a/autonomi/src/client/quote.rs b/autonomi/src/client/quote.rs index 196987d3b6..f6ad2b47e4 100644 --- a/autonomi/src/client/quote.rs +++ b/autonomi/src/client/quote.rs @@ -16,6 +16,8 @@ use libp2p::PeerId; use std::collections::HashMap; use xor_name::XorName; +pub use ant_protocol::storage::DataTypes; + /// A quote for a single address pub struct QuoteForAddress(pub(crate) Vec<(PeerId, PaymentQuote, Amount)>); @@ -72,14 +74,19 @@ pub enum CostError { impl Client { pub async fn get_store_quotes( &self, - data_type: u32, + data_type: DataTypes, content_addrs: impl Iterator, ) -> Result { // get all quotes from nodes let futures: Vec<_> = content_addrs .into_iter() .map(|(content_addr, data_size)| { - fetch_store_quote_with_retries(&self.network, content_addr, data_type, data_size) + fetch_store_quote_with_retries( + &self.network, + content_addr, + data_type.get_index(), + data_size, + ) }) .collect(); diff --git a/autonomi/src/python.rs b/autonomi/src/python.rs index f4af5d4ba7..550fcaf143 100644 --- a/autonomi/src/python.rs +++ b/autonomi/src/python.rs @@ -187,12 +187,15 @@ impl Client { counter: u32, target: &PyPointerTarget, key: &PySecretKey, - wallet: &Wallet, + payment_option: &PaymentOption, ) -> PyResult { let rt = tokio::runtime::Runtime::new().expect("Could not start tokio runtime"); let pointer = RustPointer::new(&key.inner, counter, target.inner.clone()); let (_price, addr) = rt - .block_on(self.inner.pointer_put(pointer, &wallet.inner)) + .block_on( + self.inner + .pointer_put(pointer, payment_option.inner.clone()), + ) .map_err(|e| PyValueError::new_err(format!("Failed to put pointer: {e}")))?; Ok(PyPointerAddress { inner: addr }) } diff --git a/autonomi/tests/external_signer.rs b/autonomi/tests/external_signer.rs index eaba749c52..fae2ebfe52 100644 --- a/autonomi/tests/external_signer.rs +++ b/autonomi/tests/external_signer.rs @@ -11,7 +11,8 @@ use autonomi::client::payment::{receipt_from_store_quotes, Receipt}; use autonomi::client::quote::StoreQuote; use autonomi::client::vault::user_data::USER_DATA_VAULT_CONTENT_IDENTIFIER; use autonomi::client::vault::VaultSecretKey; -use autonomi::{Client, Wallet}; +use autonomi::vault::UserData; +use autonomi::{Client, Scratchpad, Wallet}; use bytes::Bytes; use std::collections::BTreeMap; use std::time::Duration; @@ -30,19 +31,13 @@ async fn pay_for_data(client: &Client, wallet: &Wallet, data: Bytes) -> eyre::Re xor_names.push((*chunk.name(), chunk.serialised_size())); } - pay_for_content_addresses( - client, - wallet, - DataTypes::Chunk.get_index(), - xor_names.into_iter(), - ) - .await + pay_for_content_addresses(client, wallet, DataTypes::Chunk, xor_names.into_iter()).await } async fn pay_for_content_addresses( client: &Client, wallet: &Wallet, - data_types: u32, + data_types: DataTypes, content_addrs: impl Iterator + Clone, ) -> eyre::Result { let (quotes, quote_payments, _free_chunks) = client @@ -138,32 +133,26 @@ async fn external_signer_put() -> eyre::Result<()> { let vault_key = VaultSecretKey::random(); - let mut user_data = client - .get_user_data_from_vault(&vault_key) - .await - .unwrap_or_default(); + let mut user_data = UserData::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.public_key(), *USER_DATA_VAULT_CONTENT_IDENTIFIER) - .await?; - - assert!(is_new, "Scratchpad is not new"); + let scratchpad = Scratchpad::new( + &vault_key, + *USER_DATA_VAULT_CONTENT_IDENTIFIER, + &user_data.to_bytes()?, + 0, + ); - let scratch_addresses = if is_new { - vec![(scratch.xorname(), scratch.payload_size())] - } else { - vec![] - }; + let scratch_addresses = vec![(scratchpad.xorname(), scratchpad.payload_size())]; let receipt = pay_for_content_addresses( &client, &wallet, - DataTypes::Scratchpad.get_index(), + DataTypes::Scratchpad, scratch_addresses.into_iter(), ) .await?; diff --git a/autonomi/tests/graph.rs b/autonomi/tests/graph.rs index 1f50ea03f8..e3055c6e1d 100644 --- a/autonomi/tests/graph.rs +++ b/autonomi/tests/graph.rs @@ -8,7 +8,10 @@ use ant_logging::LogBuilder; use autonomi::{ - client::graph::{GraphEntry, GraphError}, + client::{ + graph::{GraphEntry, GraphError}, + payment::PaymentOption, + }, Client, }; use eyre::Result; @@ -30,7 +33,10 @@ async fn graph_entry_put() -> Result<()> { println!("graph_entry cost: {cost}"); // put the graph_entry - client.graph_entry_put(graph_entry.clone(), &wallet).await?; + let payment_option = PaymentOption::from(&wallet); + client + .graph_entry_put(graph_entry.clone(), payment_option) + .await?; println!("graph_entry put 1"); // wait for the graph_entry to be replicated @@ -44,7 +50,10 @@ async fn graph_entry_put() -> Result<()> { // try put another graph_entry with the same address let content2 = [1u8; 32]; let graph_entry2 = GraphEntry::new(key.public_key(), vec![], content2, vec![], &key); - let res = client.graph_entry_put(graph_entry2.clone(), &wallet).await; + let payment_option = PaymentOption::from(&wallet); + let res = client + .graph_entry_put(graph_entry2.clone(), payment_option) + .await; assert!(matches!( res, diff --git a/autonomi/tests/pointer.rs b/autonomi/tests/pointer.rs index 4ca6bfb4bd..30269cc51c 100644 --- a/autonomi/tests/pointer.rs +++ b/autonomi/tests/pointer.rs @@ -7,6 +7,7 @@ // permissions and limitations relating to use of the SAFE Network Software. use ant_logging::LogBuilder; +use autonomi::client::payment::PaymentOption; use autonomi::AttoTokens; use autonomi::{ chunk::ChunkAddress, @@ -35,7 +36,8 @@ async fn pointer_put_manual() -> Result<()> { println!("pointer cost: {cost}"); // put the pointer - let (cost, addr) = client.pointer_put(pointer.clone(), &wallet).await?; + let payment_option = PaymentOption::from(&wallet); + let (cost, addr) = client.pointer_put(pointer.clone(), payment_option).await?; assert_eq!(addr, pointer.address()); println!("pointer put 1 cost: {cost}"); @@ -50,7 +52,8 @@ async fn pointer_put_manual() -> Result<()> { // try update pointer and make it point to itself let target2 = PointerTarget::PointerAddress(addr); let pointer2 = Pointer::new(&key, 1, target2); - let (cost, _) = client.pointer_put(pointer2.clone(), &wallet).await?; + let payment_option = PaymentOption::from(&wallet); + let (cost, _) = client.pointer_put(pointer2.clone(), payment_option).await?; assert_eq!(cost, AttoTokens::zero()); println!("pointer put 2 cost: {cost}"); @@ -82,7 +85,10 @@ async fn pointer_put() -> Result<()> { println!("pointer cost: {cost}"); // put the pointer - let (cost, addr) = client.pointer_create(&key, target.clone(), &wallet).await?; + let payment_option = PaymentOption::from(&wallet); + let (cost, addr) = client + .pointer_create(&key, target.clone(), payment_option) + .await?; println!("pointer create cost: {cost}"); // wait for the pointer to be replicated @@ -96,7 +102,6 @@ async fn pointer_put() -> Result<()> { // try update pointer and make it point to itself let target2 = PointerTarget::PointerAddress(addr); client.pointer_update(&key, target2.clone()).await?; - println!("pointer update cost: {cost}"); // wait for the pointer to be replicated tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; diff --git a/autonomi/tests/scratchpad.rs b/autonomi/tests/scratchpad.rs new file mode 100644 index 0000000000..9093128819 --- /dev/null +++ b/autonomi/tests/scratchpad.rs @@ -0,0 +1,209 @@ +// 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. + +use ant_logging::LogBuilder; +use autonomi::client::payment::PaymentOption; +use autonomi::scratchpad::ScratchpadError; +use autonomi::AttoTokens; +use autonomi::{ + client::scratchpad::{Bytes, Scratchpad}, + Client, +}; +use eyre::Result; +use test_utils::evm::get_funded_wallet; + +#[tokio::test] +async fn scratchpad_put_manual() -> Result<()> { + let _log_appender_guard = LogBuilder::init_single_threaded_tokio_test("scratchpad", false); + + let client = Client::init_local().await?; + let wallet = get_funded_wallet(); + + let key = bls::SecretKey::random(); + let public_key = key.public_key(); + let content = Bytes::from("Massive Array of Internet Disks"); + let scratchpad = Scratchpad::new(&key, 42, &content, 0); + + // estimate the cost of the scratchpad + let cost = client.scratchpad_cost(&public_key).await?; + println!("scratchpad cost: {cost}"); + + // put the scratchpad + let payment_option = PaymentOption::from(&wallet); + let (cost, addr) = client + .scratchpad_put(scratchpad.clone(), payment_option) + .await?; + assert_eq!(addr, *scratchpad.address()); + println!("scratchpad put 1 cost: {cost}"); + + // wait for the scratchpad to be replicated + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; + + // check that the scratchpad is stored + let got = client.scratchpad_get(&addr).await?; + assert_eq!(got, scratchpad.clone()); + println!("scratchpad got 1"); + + // check that the content is decrypted correctly + let got_content = got.decrypt_data(&key)?; + assert_eq!(got_content, content); + + // try update scratchpad + let content2 = Bytes::from("Secure Access For Everyone"); + let scratchpad2 = Scratchpad::new(&key, 42, &content2, 1); + let payment_option = PaymentOption::from(&wallet); + let (cost, _) = client + .scratchpad_put(scratchpad2.clone(), payment_option) + .await?; + assert_eq!(cost, AttoTokens::zero()); + println!("scratchpad put 2 cost: {cost}"); + + // wait for the scratchpad to be replicated + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; + + // check that the scratchpad is updated + let got = client.scratchpad_get(&addr).await?; + assert_eq!(got, scratchpad2.clone()); + println!("scratchpad got 2"); + + // check that the content is decrypted correctly + let got_content2 = got.decrypt_data(&key)?; + assert_eq!(got_content2, content2); + + Ok(()) +} + +#[tokio::test] +async fn scratchpad_put() -> Result<()> { + let _log_appender_guard = LogBuilder::init_single_threaded_tokio_test("scratchpad", false); + + let client = Client::init_local().await?; + let wallet = get_funded_wallet(); + + let key = bls::SecretKey::random(); + let public_key = key.public_key(); + let content = Bytes::from("what's the meaning of life the universe and everything?"); + let content_type = 42; + + // estimate the cost of the scratchpad + let cost = client.scratchpad_cost(&public_key).await?; + println!("scratchpad cost: {cost}"); + + // put the scratchpad + let payment_option = PaymentOption::from(&wallet); + let (cost, addr) = client + .scratchpad_create(&key, content_type, &content, payment_option) + .await?; + println!("scratchpad create cost: {cost}"); + + // wait for the scratchpad to be replicated + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; + + // check that the scratchpad is stored + let got = client.scratchpad_get(&addr).await?; + assert_eq!(*got.owner(), public_key); + assert_eq!(got.data_encoding(), content_type); + assert_eq!(got.decrypt_data(&key), Ok(content.clone())); + assert_eq!(got.counter(), 0); + assert!(got.verify()); + println!("scratchpad got 1"); + + // check that the content is decrypted correctly + let got_content = got.decrypt_data(&key)?; + assert_eq!(got_content, content); + + // try update scratchpad + let content2 = Bytes::from("42"); + client + .scratchpad_update(&key, content_type, &content2) + .await?; + + // wait for the scratchpad to be replicated + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; + + // check that the scratchpad is updated + let got = client.scratchpad_get(&addr).await?; + assert_eq!(*got.owner(), public_key); + assert_eq!(got.data_encoding(), content_type); + assert_eq!(got.decrypt_data(&key), Ok(content2.clone())); + assert_eq!(got.counter(), 1); + assert!(got.verify()); + println!("scratchpad got 2"); + + // check that the content is decrypted correctly + let got_content2 = got.decrypt_data(&key)?; + assert_eq!(got_content2, content2); + Ok(()) +} + +#[tokio::test] +async fn scratchpad_errors() -> Result<()> { + let _log_appender_guard = LogBuilder::init_single_threaded_tokio_test("scratchpad", false); + + let client = Client::init_local().await?; + let wallet = get_funded_wallet(); + + let key = bls::SecretKey::random(); + let content = Bytes::from("what's the meaning of life the universe and everything?"); + let content_type = 42; + + // try update scratchpad, it should fail as we haven't created it + let res = client.scratchpad_update(&key, content_type, &content).await; + assert!(matches!( + res, + Err(ScratchpadError::CannotUpdateNewScratchpad) + )); + + // put the scratchpad normally + let payment_option = PaymentOption::from(&wallet); + let (cost, addr) = client + .scratchpad_create(&key, content_type, &content, payment_option) + .await?; + println!("scratchpad create cost: {cost}"); + + // wait for the scratchpad to be replicated + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; + + // check that the scratchpad is stored + let got = client.scratchpad_get(&addr).await?; + assert_eq!(*got.owner(), key.public_key()); + assert_eq!(got.data_encoding(), content_type); + assert_eq!(got.decrypt_data(&key), Ok(content.clone())); + assert_eq!(got.counter(), 0); + assert!(got.verify()); + println!("scratchpad got 1"); + + // try create scratchpad at the same address + let fork_content = Bytes::from("Fork"); + let payment_option = PaymentOption::from(&wallet); + let res = client + .scratchpad_create(&key, content_type, &fork_content, payment_option) + .await; + println!("Scratchpad create should fail here: {res:?}"); + assert!(matches!( + res, + Err(ScratchpadError::ScratchpadAlreadyExists(_)) + )); + + // wait for the scratchpad to be replicated + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; + + // check that the scratchpad is stored with original content + let got = client.scratchpad_get(&addr).await?; + assert_eq!(*got.owner(), key.public_key()); + assert_eq!(got.data_encoding(), content_type); + assert_eq!(got.decrypt_data(&key), Ok(content.clone())); + assert_eq!(got.counter(), 0); + assert!(got.verify()); + println!("scratchpad got 1"); + + // check that the content is decrypted correctly and matches the original + let got_content = got.decrypt_data(&key)?; + assert_eq!(got_content, content); + Ok(()) +}