From f93e902c83d2d3fb5c38d4e773689d8031ef2b65 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Mon, 9 Dec 2024 14:28:14 +0100 Subject: [PATCH 1/3] feat: pay returns a receipt --- ant-evm/src/data_payments.rs | 33 ++++++++++++++++++++------ ant-evm/src/lib.rs | 2 +- autonomi/src/client/payment.rs | 42 +++++++++++++++++++++++++++++++--- autonomi/src/client/quote.rs | 21 +++++++++++++---- autonomi/src/client/utils.rs | 9 +++++--- 5 files changed, 89 insertions(+), 18 deletions(-) diff --git a/ant-evm/src/data_payments.rs b/ant-evm/src/data_payments.rs index 47476893aa..9f959a93fa 100644 --- a/ant-evm/src/data_payments.rs +++ b/ant-evm/src/data_payments.rs @@ -8,7 +8,9 @@ use crate::EvmError; use evmlib::{ - common::{Address as RewardsAddress, QuoteHash}, quoting_metrics::QuotingMetrics, utils::dummy_address + common::{Address as RewardsAddress, QuoteHash}, + quoting_metrics::QuotingMetrics, + utils::dummy_address, }; use libp2p::{identity::PublicKey, PeerId}; use serde::{Deserialize, Serialize}; @@ -31,31 +33,48 @@ impl EncodedPeerId { pub fn to_peer_id(&self) -> Result { match PublicKey::try_decode_protobuf(&self.0) { Ok(pub_key) => Ok(PeerId::from_public_key(&pub_key)), - Err(e) => Err(e) + Err(e) => Err(e), } } } +// TODO: @anselme is this conversion right? +impl From for EncodedPeerId { + fn from(peer_id: PeerId) -> Self { + let bytes = peer_id.to_bytes(); + EncodedPeerId(bytes) + } +} + /// The proof of payment for a data payment #[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)] pub struct ProofOfPayment { - peer_quotes: Vec<(EncodedPeerId, PaymentQuote)> + pub peer_quotes: Vec<(EncodedPeerId, PaymentQuote)>, } impl ProofOfPayment { /// returns a short digest of the proof of payment to use for verification pub fn digest(&self) -> Vec<(QuoteHash, QuotingMetrics, RewardsAddress)> { - self.peer_quotes.clone().into_iter().map(|(_, quote)| (quote.hash(), quote.quoting_metrics, quote.rewards_address)).collect() + self.peer_quotes + .clone() + .into_iter() + .map(|(_, quote)| (quote.hash(), quote.quoting_metrics, quote.rewards_address)) + .collect() } /// returns the list of payees pub fn payees(&self) -> Vec { - self.peer_quotes.iter().filter_map(|(peer_id, _)| peer_id.to_peer_id().ok()).collect() + self.peer_quotes + .iter() + .filter_map(|(peer_id, _)| peer_id.to_peer_id().ok()) + .collect() } /// has the quote expired pub fn has_expired(&self) -> bool { - self.peer_quotes.iter().any(|(_, quote)| quote.has_expired()) + self.peer_quotes + .iter() + .any(|(_, quote)| quote.has_expired()) } /// verifies the proof of payment is valid for the given peer id @@ -72,7 +91,7 @@ impl ProofOfPayment { Err(e) => { warn!("Invalid encoded peer id: {e}"); return false; - }, + } }; if !quote.check_is_signed_by_claimed_peer(peer_id) { return false; diff --git a/ant-evm/src/lib.rs b/ant-evm/src/lib.rs index 30a42b34d4..10f557e395 100644 --- a/ant-evm/src/lib.rs +++ b/ant-evm/src/lib.rs @@ -29,7 +29,7 @@ mod amount; mod data_payments; mod error; -pub use data_payments::{PaymentQuote, ProofOfPayment, QUOTE_EXPIRATION_SECS}; +pub use data_payments::{EncodedPeerId, PaymentQuote, ProofOfPayment, QUOTE_EXPIRATION_SECS}; pub use evmlib::quoting_metrics::QuotingMetrics; /// Types used in the public API diff --git a/autonomi/src/client/payment.rs b/autonomi/src/client/payment.rs index 97416e3c09..48c199c4a6 100644 --- a/autonomi/src/client/payment.rs +++ b/autonomi/src/client/payment.rs @@ -1,11 +1,47 @@ use crate::client::data::PayError; +use crate::client::quote::StoreQuote; use crate::Client; -use ant_evm::{AttoTokens, EvmWallet, ProofOfPayment}; -use std::collections::HashMap; +use ant_evm::{AttoTokens, EncodedPeerId, EvmWallet, ProofOfPayment, QuoteHash, TxHash}; +use std::collections::{BTreeMap, HashMap}; use xor_name::XorName; /// Contains the proof of payments for each XOR address and the amount paid -pub type Receipt = HashMap>; +pub type Receipt = HashMap; + +pub fn receipt_from_store_quotes_and_payments( + quotes: StoreQuote, + payments: BTreeMap, +) -> Receipt { + let mut receipt = Receipt::new(); + + for (content_addr, quote_for_address) in quotes.0 { + let price = AttoTokens::from_atto(quote_for_address.price()); + + let mut proof_of_payment = ProofOfPayment { + peer_quotes: vec![], + }; + + for (peer_id, quote, _amount) in quote_for_address.0 { + // skip quotes that haven't been paid + if !payments.contains_key("e.hash()) { + continue; + } + + proof_of_payment + .peer_quotes + .push((EncodedPeerId::from(peer_id), quote)); + } + + // skip empty proofs + if proof_of_payment.peer_quotes.is_empty() { + continue; + } + + receipt.insert(content_addr, (proof_of_payment, price)); + } + + receipt +} /// Payment options for data payments. #[derive(Clone)] diff --git a/autonomi/src/client/quote.rs b/autonomi/src/client/quote.rs index 1e5e6b80be..f1bc67e61a 100644 --- a/autonomi/src/client/quote.rs +++ b/autonomi/src/client/quote.rs @@ -17,7 +17,7 @@ use std::collections::HashMap; use xor_name::XorName; /// A quote for a single address -pub struct QuoteForAddress(Vec<(PeerId, PaymentQuote, Amount)>); +pub struct QuoteForAddress(pub(crate) Vec<(PeerId, PaymentQuote, Amount)>); impl QuoteForAddress { pub fn price(&self) -> Amount { @@ -26,7 +26,7 @@ impl QuoteForAddress { } /// A quote for many addresses -pub struct StoreQuote(HashMap); +pub struct StoreQuote(pub(crate) HashMap); impl StoreQuote { pub fn price(&self) -> Amount { @@ -87,10 +87,23 @@ impl Client { let second = (*p2, q2.clone(), Amount::ZERO); // pay for the rest - quotes_to_pay_per_addr.insert(content_addr, QuoteForAddress(vec![first, second, third.clone(), fourth.clone(), fifth.clone()])); + quotes_to_pay_per_addr.insert( + content_addr, + QuoteForAddress(vec![ + first, + second, + third.clone(), + fourth.clone(), + fifth.clone(), + ]), + ); } _ => { - return Err(CostError::NotEnoughNodeQuotes(content_addr, prices.len(), MINIMUM_QUOTES_TO_PAY)); + return Err(CostError::NotEnoughNodeQuotes( + content_addr, + prices.len(), + MINIMUM_QUOTES_TO_PAY, + )); } } } diff --git a/autonomi/src/client/utils.rs b/autonomi/src/client/utils.rs index c187b5bb18..00e7e464e8 100644 --- a/autonomi/src/client/utils.rs +++ b/autonomi/src/client/utils.rs @@ -6,7 +6,7 @@ // 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::client::payment::{receipt_from_store_quotes_and_payments, Receipt}; use ant_evm::{EvmNetwork, EvmWallet, ProofOfPayment}; use ant_networking::{GetRecordCfg, PutRecordCfg, VerificationKind}; use ant_protocol::{ @@ -158,7 +158,9 @@ impl Client { content_addrs: impl Iterator, wallet: &EvmWallet, ) -> Result { - let quotes = self.get_store_quotes(wallet.network(), content_addrs).await?; + let quotes = self + .get_store_quotes(wallet.network(), content_addrs.clone()) + .await?; // Make sure nobody else can use the wallet while we are paying debug!("Waiting for wallet lock"); @@ -168,7 +170,6 @@ impl Client { // TODO: the error might contain some succeeded quote payments as well. These should be returned on err, so that they can be skipped when retrying. // TODO: retry when it fails? // Execute chunk payments - // NB TODO: make this return a Receipt or something that can turn into a Receipt @mick let payments = wallet .pay_for_quotes(quotes.payments()) .await @@ -178,6 +179,8 @@ impl Client { drop(lock_guard); debug!("Unlocked wallet"); + let receipt = receipt_from_store_quotes_and_payments(quotes, payments); + let skipped_chunks = content_addrs.count() - quotes.len(); trace!( "Chunk payments of {} chunks completed. {} chunks were free / already paid for", From 918d236183fd05a2ea4272a57aedb5b8c40c35b4 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Mon, 9 Dec 2024 18:52:27 +0100 Subject: [PATCH 2/3] chore: autonomi compiles! --- autonomi/src/client/data.rs | 23 +++--- autonomi/src/client/data_private.rs | 2 +- autonomi/src/client/mod.rs | 7 ++ autonomi/src/client/payment.rs | 2 +- autonomi/src/client/quote.rs | 5 +- autonomi/src/client/registers.rs | 35 +++++---- autonomi/src/client/utils.rs | 26 ++++--- autonomi/src/client/vault.rs | 12 +-- evmlib/src/contract/payment_vault/error.rs | 2 + .../src/contract/payment_vault/interface.rs | 6 ++ evmlib/src/contract/payment_vault/mod.rs | 38 +++++++++- evmlib/src/lib.rs | 13 +--- evmlib/src/transaction.rs | 76 ------------------- 13 files changed, 115 insertions(+), 132 deletions(-) delete mode 100644 evmlib/src/transaction.rs diff --git a/autonomi/src/client/data.rs b/autonomi/src/client/data.rs index 494f3de323..bf150f41c9 100644 --- a/autonomi/src/client/data.rs +++ b/autonomi/src/client/data.rs @@ -8,17 +8,17 @@ use bytes::Bytes; use libp2p::kad::Quorum; +use std::collections::HashSet; -use std::collections::{HashMap, HashSet}; use std::sync::LazyLock; use xor_name::XorName; -use crate::client::payment::PaymentOption; +use crate::client::payment::{PaymentOption, Receipt}; use crate::client::utils::process_tasks_with_max_concurrency; use crate::client::{ClientEvent, UploadSummary}; use crate::{self_encryption::encrypt, Client}; +use ant_evm::EvmWalletError; use ant_evm::{Amount, AttoTokens}; -use ant_evm::{EvmWalletError, ProofOfPayment}; use ant_networking::{GetRecordCfg, NetworkError}; use ant_protocol::{ storage::{try_deserialize_record, Chunk, ChunkAddress, RecordHeader, RecordKind}, @@ -84,6 +84,8 @@ pub enum PutError { VaultBadOwner, #[error("Payment unexpectedly invalid for {0:?}")] PaymentUnexpectedlyInvalid(NetworkAddress), + #[error("The payment proof contains no payees.")] + PayeesMissing, } /// Errors that can occur during the pay operation. @@ -198,7 +200,7 @@ impl Client { if let Some(channel) = self.client_event_sender.as_ref() { let tokens_spent = receipt .values() - .map(|proof| proof.quote.cost.as_atto()) + .map(|(_proof, price)| price.as_atto()) .sum::(); let summary = UploadSummary { @@ -261,16 +263,19 @@ impl Client { content_addrs.len() ); - let cost_map = self + let store_quote = self .get_store_quotes(content_addrs.into_iter()) .await .inspect_err(|err| error!("Error getting store quotes: {err:?}"))?; + let total_cost = AttoTokens::from_atto( - cost_map + store_quote + .0 .values() - .map(|quote| quote.2.cost.as_atto()) + .map(|quote| quote.price()) .sum::(), ); + Ok(total_cost) } @@ -278,7 +283,7 @@ impl Client { pub(crate) async fn upload_chunks_with_retries<'a>( &self, mut chunks: Vec<&'a Chunk>, - receipt: &HashMap, + receipt: &Receipt, ) -> Vec<(&'a Chunk, PutError)> { let mut current_attempt: usize = 1; @@ -288,7 +293,7 @@ impl Client { let self_clone = self.clone(); let address = *chunk.address(); - let Some(proof) = receipt.get(chunk.name()) else { + let Some((proof, _)) = receipt.get(chunk.name()) else { debug!("Chunk at {address:?} was already paid for so skipping"); continue; }; diff --git a/autonomi/src/client/data_private.rs b/autonomi/src/client/data_private.rs index 5f2dd1793c..d1288bb193 100644 --- a/autonomi/src/client/data_private.rs +++ b/autonomi/src/client/data_private.rs @@ -100,7 +100,7 @@ impl Client { if let Some(channel) = self.client_event_sender.as_ref() { let tokens_spent = receipt .values() - .map(|proof| proof.quote.cost.as_atto()) + .map(|(_proof, price)| price.as_atto()) .sum::(); let summary = UploadSummary { diff --git a/autonomi/src/client/mod.rs b/autonomi/src/client/mod.rs index 533fad9e70..9ce93b9150 100644 --- a/autonomi/src/client/mod.rs +++ b/autonomi/src/client/mod.rs @@ -37,6 +37,7 @@ mod utils; pub use ant_evm::Amount; +use crate::EvmNetwork; use ant_networking::{interval, multiaddr_is_global, Network, NetworkBuilder, NetworkEvent}; use ant_protocol::{version::IDENTIFY_PROTOCOL_STR, CLOSE_GROUP_SIZE}; use libp2p::{identity::Keypair, Multiaddr}; @@ -67,6 +68,7 @@ const CLIENT_EVENT_CHANNEL_SIZE: usize = 100; pub struct Client { pub(crate) network: Network, pub(crate) client_event_sender: Arc>>, + pub(crate) evm_network: EvmNetwork, } /// Error returned by [`Client::connect`]. @@ -120,6 +122,7 @@ impl Client { Ok(Self { network, client_event_sender: Arc::new(None), + evm_network: Default::default(), }) } @@ -130,6 +133,10 @@ impl Client { self.client_event_sender = Arc::new(Some(client_event_sender)); client_event_receiver } + + pub fn set_evm_network(&mut self, evm_network: EvmNetwork) { + self.evm_network = evm_network; + } } fn build_client_and_run_swarm(local: bool) -> (Network, mpsc::Receiver) { diff --git a/autonomi/src/client/payment.rs b/autonomi/src/client/payment.rs index 48c199c4a6..509615fb20 100644 --- a/autonomi/src/client/payment.rs +++ b/autonomi/src/client/payment.rs @@ -71,7 +71,7 @@ impl From for PaymentOption { impl Client { pub(crate) async fn pay_for_content_addrs( &self, - content_addrs: impl Iterator, + content_addrs: impl Iterator + Clone, payment_option: PaymentOption, ) -> Result { match payment_option { diff --git a/autonomi/src/client/quote.rs b/autonomi/src/client/quote.rs index f1bc67e61a..514cf9c4d5 100644 --- a/autonomi/src/client/quote.rs +++ b/autonomi/src/client/quote.rs @@ -7,7 +7,6 @@ // permissions and limitations relating to use of the SAFE Network Software. use super::{data::CostError, Client}; -use crate::EvmNetwork; use ant_evm::payment_vault::get_market_price; use ant_evm::{Amount, PaymentQuote, QuotePayment}; use ant_networking::{Network, NetworkError}; @@ -51,7 +50,6 @@ impl StoreQuote { impl Client { pub(crate) async fn get_store_quotes( &self, - evm_network: &EvmNetwork, content_addrs: impl Iterator, ) -> Result { // get all quotes from nodes @@ -68,7 +66,8 @@ impl Client { let mut prices = vec![]; for (peer, quote) in raw_quotes { // NB TODO @mick we need to batch this smart contract call - let price = get_market_price(evm_network, quote.quoting_metrics.clone()).await?; + let price = + get_market_price(&self.evm_network, quote.quoting_metrics.clone()).await?; prices.push((peer, quote, price)); } diff --git a/autonomi/src/client/registers.rs b/autonomi/src/client/registers.rs index 7476323ee9..d7237c27fa 100644 --- a/autonomi/src/client/registers.rs +++ b/autonomi/src/client/registers.rs @@ -11,7 +11,6 @@ use crate::client::Client; use crate::client::ClientEvent; use crate::client::UploadSummary; -use ant_evm::EvmNetwork; pub use ant_registers::{Permissions as RegisterPermissions, RegisterAddress}; pub use bls::SecretKey as RegisterSecretKey; @@ -50,6 +49,8 @@ pub enum RegisterError { CouldNotSign(#[source] ant_registers::Error), #[error("Received invalid quote from node, this node is possibly malfunctioning, try another node by trying another register name")] InvalidQuote, + #[error("The payment proof contains no payees.")] + PayeesMissing, } #[derive(Clone, Debug)] @@ -225,7 +226,6 @@ impl Client { /// Get the cost to create a register pub async fn register_cost( &self, - evm_network: &EvmNetwork, name: String, owner: RegisterSecretKey, ) -> Result { @@ -239,11 +239,13 @@ impl Client { // get cost to store register // NB TODO: register should be priced differently from other data - let cost_map = self.get_store_quotes(evm_network, std::iter::once(reg_xor)).await?; + let store_quote = self.get_store_quotes(std::iter::once(reg_xor)).await?; + let total_cost = AttoTokens::from_atto( - cost_map + store_quote + .0 .values() - .map(|quote| quote.2.cost.as_atto()) + .map(|quote| quote.price()) .sum::(), ); @@ -300,18 +302,24 @@ impl Client { .inspect_err(|err| { error!("Failed to pay for register at address: {address} : {err}") })?; - let proof = if let Some(proof) = payment_proofs.get(®_xor) { - proof + let (proof, price) = if let Some((proof, price)) = payment_proofs.get(®_xor) { + (proof, price) } else { // register was skipped, meaning it was already paid for error!("Register at address: {address} was already paid for"); return Err(RegisterError::Network(NetworkError::RegisterAlreadyExists)); }; - let payee = proof - .to_peer_id_payee() - .ok_or(RegisterError::InvalidQuote) - .inspect_err(|err| error!("Failed to get payee from payment proof: {err}"))?; + let payees = proof.payees(); + + if payees.is_empty() { + error!( + "Failed to get payees from payment proof: {:?}", + RegisterError::PayeesMissing + ); + return Err(RegisterError::PayeesMissing); + } + let signed_register = register.signed_reg.clone(); let record = Record { @@ -333,10 +341,11 @@ impl Client { expected_holders: Default::default(), is_register: true, }; + let put_cfg = PutRecordCfg { put_quorum: Quorum::All, retry_strategy: None, - use_put_record_to: Some(vec![payee]), + use_put_record_to: Some(payees), // CODE REVIEW: should we put to all or just one here? verification: Some((VerificationKind::Network, get_cfg)), }; @@ -351,7 +360,7 @@ impl Client { if let Some(channel) = self.client_event_sender.as_ref() { let summary = UploadSummary { record_count: 1, - tokens_spent: proof.quote.cost.as_atto(), + tokens_spent: price.as_atto(), }; if let Err(err) = channel.send(ClientEvent::UploadComplete(summary)).await { error!("Failed to send client event: {err}"); diff --git a/autonomi/src/client/utils.rs b/autonomi/src/client/utils.rs index 00e7e464e8..3d4b0c7e29 100644 --- a/autonomi/src/client/utils.rs +++ b/autonomi/src/client/utils.rs @@ -7,7 +7,7 @@ // permissions and limitations relating to use of the SAFE Network Software. use crate::client::payment::{receipt_from_store_quotes_and_payments, Receipt}; -use ant_evm::{EvmNetwork, EvmWallet, ProofOfPayment}; +use ant_evm::{EvmWallet, ProofOfPayment}; use ant_networking::{GetRecordCfg, PutRecordCfg, VerificationKind}; use ant_protocol::{ messages::ChunkProof, @@ -99,9 +99,13 @@ impl Client { chunk: &Chunk, payment: ProofOfPayment, ) -> Result<(), PutError> { - let storing_node = payment.to_peer_id_payee().expect("Missing node Peer ID"); + let storing_nodes = payment.payees(); - debug!("Storing chunk: {chunk:?} to {:?}", storing_node); + if storing_nodes.is_empty() { + return Err(PutError::PayeesMissing); + } + + debug!("Storing chunk: {chunk:?} to {:?}", storing_nodes); let key = chunk.network_address().to_record_key(); @@ -146,21 +150,21 @@ impl Client { let put_cfg = PutRecordCfg { put_quorum: Quorum::One, retry_strategy: Some(RetryStrategy::Balanced), - use_put_record_to: Some(vec![storing_node]), + use_put_record_to: Some(storing_nodes), // CODE REVIEW: do we put to all payees or just one? verification, }; + Ok(self.network.put_record(record, &put_cfg).await?) } /// Pay for the chunks and get the proof of payment. pub(crate) async fn pay( &self, - content_addrs: impl Iterator, + content_addrs: impl Iterator + Clone, wallet: &EvmWallet, ) -> Result { - let quotes = self - .get_store_quotes(wallet.network(), content_addrs.clone()) - .await?; + let number_of_content_addrs = content_addrs.clone().count(); + let quotes = self.get_store_quotes(content_addrs).await?; // Make sure nobody else can use the wallet while we are paying debug!("Waiting for wallet lock"); @@ -179,15 +183,15 @@ impl Client { drop(lock_guard); debug!("Unlocked wallet"); - let receipt = receipt_from_store_quotes_and_payments(quotes, payments); - - let skipped_chunks = content_addrs.count() - quotes.len(); + let skipped_chunks = number_of_content_addrs - quotes.len(); trace!( "Chunk payments of {} chunks completed. {} chunks were free / already paid for", quotes.len(), skipped_chunks ); + let receipt = receipt_from_store_quotes_and_payments(quotes, payments); + Ok(receipt) } } diff --git a/autonomi/src/client/vault.rs b/autonomi/src/client/vault.rs index baa86ed120..e960fff64e 100644 --- a/autonomi/src/client/vault.rs +++ b/autonomi/src/client/vault.rs @@ -151,11 +151,13 @@ impl Client { let vault_xor = scratch.network_address().as_xorname().unwrap_or_default(); // NB TODO: vault should be priced differently from other data - let cost_map = self.get_store_quotes(std::iter::once(vault_xor)).await?; + let store_quote = self.get_store_quotes(std::iter::once(vault_xor)).await?; + let total_cost = AttoTokens::from_atto( - cost_map + store_quote + .0 .values() - .map(|quote| quote.2.cost.as_atto()) + .map(|quote| quote.price()) .sum::(), ); @@ -196,12 +198,12 @@ impl Client { error!("Failed to pay for new vault at addr: {scratch_address:?} : {err}"); })?; - let proof = match receipt.values().next() { + let (proof, price) = match receipt.values().next() { Some(proof) => proof, None => return Err(PutError::PaymentUnexpectedlyInvalid(scratch_address)), }; - total_cost = proof.quote.cost; + total_cost = price.clone(); Record { key: scratch_key, diff --git a/evmlib/src/contract/payment_vault/error.rs b/evmlib/src/contract/payment_vault/error.rs index 0441b5b1ea..6c94c680f1 100644 --- a/evmlib/src/contract/payment_vault/error.rs +++ b/evmlib/src/contract/payment_vault/error.rs @@ -8,4 +8,6 @@ pub enum Error { RpcError(#[from] RpcError), #[error(transparent)] PendingTransactionError(#[from] alloy::providers::PendingTransactionError), + #[error("Payment is invalid.")] + PaymentInvalid, } diff --git a/evmlib/src/contract/payment_vault/interface.rs b/evmlib/src/contract/payment_vault/interface.rs index d99811e01a..9f2d6f3490 100644 --- a/evmlib/src/contract/payment_vault/interface.rs +++ b/evmlib/src/contract/payment_vault/interface.rs @@ -10,6 +10,12 @@ sol!( "abi/IPaymentVault.json" ); +pub struct PaymentVerification { + pub quote_hash: FixedBytes<32>, + pub amount_paid: Amount, + pub is_valid: bool, +} + impl From<(QuoteHash, Address, Amount)> for IPaymentVault::DataPayment { fn from(value: (QuoteHash, Address, Amount)) -> Self { Self { diff --git a/evmlib/src/contract/payment_vault/mod.rs b/evmlib/src/contract/payment_vault/mod.rs index d6afbbd91a..8ed1a9a92b 100644 --- a/evmlib/src/contract/payment_vault/mod.rs +++ b/evmlib/src/contract/payment_vault/mod.rs @@ -1,5 +1,6 @@ -use crate::common::Amount; +use crate::common::{Address, Amount, QuoteHash}; use crate::contract::payment_vault::handler::PaymentVaultHandler; +use crate::contract::payment_vault::interface::PaymentVerification; use crate::quoting_metrics::QuotingMetrics; use crate::utils::http_provider; use crate::Network; @@ -20,3 +21,38 @@ pub async fn get_market_price( let payment_vault = PaymentVaultHandler::new(*network.data_payments_address(), provider); payment_vault.get_quote(quoting_metrics).await } + +/// Helper function to verify whether a data payment is valid +pub async fn verify_data_payment( + network: &Network, + owned_quote_hashes: Vec, + payment: Vec<(QuoteHash, QuotingMetrics, Address)>, +) -> Result { + let provider = http_provider(network.rpc_url().clone()); + let payment_vault = PaymentVaultHandler::new(*network.data_payments_address(), provider); + + let mut amount = Amount::ZERO; + + // TODO: @mick change this for loop to a batch when the smart contract changes + for (quote_hash, quoting_metrics, rewards_address) in payment { + let payment_verification: PaymentVerification = payment_vault + .verify_payment(quoting_metrics, (quote_hash, rewards_address, Amount::ZERO)) + .await + .map(|is_valid| PaymentVerification { + quote_hash, + amount_paid: Amount::from(1), // TODO: update placeholder amount when the smart contract changes + is_valid, + })?; + + // CODE REVIEW: should we fail on a single invalid payment? + if !payment_verification.is_valid { + return Err(error::Error::PaymentInvalid); + } + + if owned_quote_hashes.contains("e_hash) { + amount += payment_verification.amount_paid; + } + } + + Ok(amount) +} diff --git a/evmlib/src/lib.rs b/evmlib/src/lib.rs index 1bc363925a..6de2343462 100644 --- a/evmlib/src/lib.rs +++ b/evmlib/src/lib.rs @@ -6,12 +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::common::{Address, QuoteHash}; -use crate::transaction::verify_data_payment; +use crate::common::Address; use alloy::primitives::address; use alloy::transports::http::reqwest; -use common::Amount; -use quoting_metrics::QuotingMetrics; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DisplayFromStr}; use std::str::FromStr; @@ -27,7 +24,6 @@ pub mod cryptography; pub mod external_signer; pub mod quoting_metrics; pub mod testnet; -pub mod transaction; pub mod utils; pub mod wallet; @@ -135,11 +131,4 @@ impl Network { Network::Custom(custom) => &custom.data_payments_address, } } - - pub async fn verify_data_payment( - &self, - payment: Vec<(QuoteHash, QuotingMetrics, Address)> - ) -> Result { - verify_data_payment(self, payment).await - } } diff --git a/evmlib/src/transaction.rs b/evmlib/src/transaction.rs deleted file mode 100644 index 12b8f680a0..0000000000 --- a/evmlib/src/transaction.rs +++ /dev/null @@ -1,76 +0,0 @@ -// 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 crate::common::{Address, Amount, QuoteHash}; -use crate::contract::payment_vault::handler::PaymentVaultHandler; -use crate::quoting_metrics::QuotingMetrics; -use crate::utils::http_provider; -use crate::{contract, Network}; -use alloy::transports::{RpcError, TransportErrorKind}; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - RpcError(#[from] RpcError), - #[error(transparent)] - PaymentVaultError(#[from] contract::payment_vault::error::Error), - #[error("Payment missing")] - PaymentMissing, -} - -/// Verify if a data payment is confirmed. -pub async fn verify_data_payment( - network: &Network, - my_quote_hashes: Vec, // TODO @mick hashes the node owns so it knows how much it received from them - payment: Vec<(QuoteHash, QuotingMetrics, Address)> -) -> Result { - let provider = http_provider(network.rpc_url().clone()); - let payment_vault = PaymentVaultHandler::new(*network.data_payments_address(), provider); - - // NB TODO @mick remove tmp loop and support verification of the whole payment at once - let mut is_paid = true; - for (quote_hash, quoting_metrics, reward_addr) in payment { - is_paid = payment_vault - .verify_payment(quoting_metrics, (quote_hash, reward_addr, Amount::ZERO)) - .await?; - } - - let amount_paid = Amount::ZERO; // NB TODO @mick we need to get the amount paid from the contract - - if is_paid { - Ok(amount_paid) - } else { - Err(Error::PaymentMissing) - } -} - -#[cfg(test)] -mod tests { - use crate::common::Address; - use crate::quoting_metrics::QuotingMetrics; - use crate::transaction::verify_data_payment; - use crate::Network; - use alloy::hex::FromHex; - use alloy::primitives::b256; - - #[tokio::test] - async fn test_verify_data_payment() { - let network = Network::ArbitrumOne; - - let quote_hash = b256!("EBD943C38C0422901D4CF22E677DD95F2591CA8D6EBFEA8BAF1BFE9FF5506ECE"); // DevSkim: ignore DS173237 - let reward_address = Address::from_hex("8AB15A43305854e4AE4E6FBEa0CD1CC0AB4ecB2A").unwrap(); // DevSkim: ignore DS173237 - - let result = verify_data_payment( - &network, - vec![(quote_hash, QuotingMetrics::default(), reward_address)] - ) - .await; - - assert!(result.is_ok(), "Error: {:?}", result.err()); - } -} From ffdaa02e60482c9e0f0f563ee1caf2d93ce5c6ed Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Mon, 9 Dec 2024 20:48:11 +0100 Subject: [PATCH 3/3] fix: put validation verify payment import and input --- ant-evm/src/data_payments.rs | 15 +++++++++++++++ ant-node/src/put_validation.rs | 19 ++++++++++++------- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/ant-evm/src/data_payments.rs b/ant-evm/src/data_payments.rs index 9f959a93fa..f11486dd0e 100644 --- a/ant-evm/src/data_payments.rs +++ b/ant-evm/src/data_payments.rs @@ -77,6 +77,21 @@ impl ProofOfPayment { .any(|(_, quote)| quote.has_expired()) } + /// Returns all quotes by given peer id + pub fn quotes_by_peer(&self, peer_id: &PeerId) -> Vec<&PaymentQuote> { + self.peer_quotes + .iter() + .filter_map(|(id, quote)| { + if let Ok(id) = id.to_peer_id() { + if id == *peer_id { + return Some(quote); + } + } + None + }) + .collect() + } + /// verifies the proof of payment is valid for the given peer id pub fn verify_for(&self, peer_id: PeerId) -> bool { // make sure I am in the list of payees diff --git a/ant-node/src/put_validation.rs b/ant-node/src/put_validation.rs index 6bfa39b496..436980323c 100644 --- a/ant-node/src/put_validation.rs +++ b/ant-node/src/put_validation.rs @@ -7,7 +7,8 @@ // permissions and limitations relating to use of the SAFE Network Software. use crate::{node::Node, Error, Marker, Result}; -use ant_evm::{AttoTokens, ProofOfPayment, QUOTE_EXPIRATION_SECS}; +use ant_evm::payment_vault::verify_data_payment; +use ant_evm::{AttoTokens, ProofOfPayment}; use ant_networking::NetworkError; use ant_protocol::storage::Transaction; use ant_protocol::{ @@ -19,7 +20,6 @@ use ant_protocol::{ }; use ant_registers::SignedRegister; use libp2p::kad::{Record, RecordKey}; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; use xor_name::XorName; impl Node { @@ -619,14 +619,19 @@ impl Node { ))); } + let owned_payment_quotes = payment + .quotes_by_peer(&self_peer_id) + .iter() + .map(|quote| quote.hash()) + .collect(); + // check if payment is valid on chain let payments_to_verify = payment.digest(); debug!("Verifying payment for record {pretty_key}"); - let reward_amount = self - .evm_network() - .verify_data_payment(payments_to_verify) - .await - .map_err(|e| Error::EvmNetwork(format!("Failed to verify chunk payment: {e}")))?; + let reward_amount = + verify_data_payment(self.evm_network(), owned_payment_quotes, payments_to_verify) + .await + .map_err(|e| Error::EvmNetwork(format!("Failed to verify chunk payment: {e}")))?; debug!("Payment of {reward_amount:?} is valid for record {pretty_key}"); // Notify `record_store` that the node received a payment.